Merge "Connect Filter Event Callback between the TunerService and TunerClient" into sc-dev
diff --git a/Android.bp b/Android.bp
index d170913..dc92586 100644
--- a/Android.bp
+++ b/Android.bp
@@ -632,6 +632,7 @@
     ],
     sdk_version: "core_platform",
     static_libs: [
+        "bouncycastle-repackaged-unbundled",
         "framework-internal-utils",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cdf5df6c..30ed7de 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -8,6 +8,7 @@
                cmds/input/
                cmds/uinput/
                core/jni/
+               libs/hwui/
                libs/input/
                native/
                services/core/jni/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e05f0b0..6ab1051 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -665,9 +665,18 @@
         WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
                 List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
             mConfigIdentifier = configIdentifier;
-            mDefaultMaxTotal = mMaxTotal = defaultMaxTotal;
+            mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT);
+            int numReserved = 0;
             for (int i = defaultMin.size() - 1; i >= 0; --i) {
                 mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
+                numReserved += defaultMin.get(i).second;
+            }
+            if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
+                // We only create new configs on boot, so this should trigger during development
+                // (before the code gets checked in), so this makes sure the hard-coded defaults
+                // make sense. DeviceConfig values will be handled gracefully in update().
+                throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
+                        + " min=" + defaultMin + " max=" + defaultMax);
             }
             for (int i = defaultMax.size() - 1; i >= 0; --i) {
                 mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
@@ -782,15 +791,10 @@
             mNumUnspecialized = mConfigMaxTotal;
             mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
             mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
-            mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP);
-            mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG);
-            calculateUnspecializedRemaining();
-        }
-
-        private void calculateUnspecializedRemaining() {
-            mNumUnspecializedRemaining = mNumUnspecialized;
+            mNumUnspecializedRemaining = mConfigMaxTotal;
             for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
-                mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
+                mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
+                        mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
             }
         }
 
@@ -873,24 +877,42 @@
             mNumUnspecialized = mConfigMaxTotal;
             final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP)
                     + mNumPendingJobs.get(WORK_TYPE_TOP);
-            final int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
+            int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
             mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop);
             mNumUnspecialized -= resTop;
             final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG);
-            final int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
+            int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
             mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
             mNumUnspecialized -= resBg;
-            calculateUnspecializedRemaining();
+
+            mNumUnspecializedRemaining = mNumUnspecialized;
+            // Account for already running jobs after we've assigned the minimum number of slots.
+            int unspecializedAssigned;
+            int extraRunning = (mNumRunningJobs.get(WORK_TYPE_TOP) - resTop);
+            if (extraRunning > 0) {
+                unspecializedAssigned = Math.max(0,
+                        Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP) - resTop,
+                                extraRunning));
+                resTop += unspecializedAssigned;
+                mNumUnspecializedRemaining -= extraRunning;
+            }
+            extraRunning = (mNumRunningJobs.get(WORK_TYPE_BG) - resBg);
+            if (extraRunning > 0) {
+                unspecializedAssigned = Math.max(0,
+                        Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG) - resBg, extraRunning));
+                resBg += unspecializedAssigned;
+                mNumUnspecializedRemaining -= extraRunning;
+            }
 
             // Assign remaining unspecialized based on ranking.
-            int unspecializedAssigned = Math.max(0,
-                    Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
-                            Math.min(mNumUnspecializedRemaining, numTop - resTop)));
+            unspecializedAssigned = Math.max(0,
+                    Math.min(mNumUnspecializedRemaining,
+                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
             mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
             mNumUnspecializedRemaining -= unspecializedAssigned;
             unspecializedAssigned = Math.max(0,
-                    Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
-                            Math.min(mNumUnspecializedRemaining, numBg - resBg)));
+                    Math.min(mNumUnspecializedRemaining,
+                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
             mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
             mNumUnspecializedRemaining -= unspecializedAssigned;
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index 73386a7..8dae210 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -157,7 +157,7 @@
     field public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
     field public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
     field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
-    field public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+    field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
     field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
     field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -722,6 +722,7 @@
     field public static final int gwpAsanMode = 16844310; // 0x1010616
     field public static final int hand_hour = 16843011; // 0x1010103
     field public static final int hand_minute = 16843012; // 0x1010104
+    field public static final int hand_second = 16844323; // 0x1010623
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
@@ -8350,6 +8351,7 @@
     method public android.appwidget.AppWidgetProviderInfo clone();
     method public int describeContents();
     method public final android.os.UserHandle getProfile();
+    method @NonNull public android.content.pm.ActivityInfo getProviderInfo();
     method @Nullable public final String loadDescription(@NonNull android.content.Context);
     method public final android.graphics.drawable.Drawable loadIcon(@NonNull android.content.Context, int);
     method public final String loadLabel(android.content.pm.PackageManager);
@@ -11868,6 +11870,7 @@
   }
 
   public class LauncherActivityInfo {
+    method @NonNull public android.content.pm.ActivityInfo getActivityInfo();
     method public android.content.pm.ApplicationInfo getApplicationInfo();
     method public android.graphics.drawable.Drawable getBadgedIcon(int);
     method public android.content.ComponentName getComponentName();
@@ -12240,6 +12243,7 @@
     method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
@@ -12468,6 +12472,7 @@
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
+    field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
     field public static final int SIGNATURE_MATCH = 0; // 0x0
     field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -16800,6 +16805,18 @@
 
 package android.hardware {
 
+  public abstract class Battery {
+    ctor public Battery();
+    method @FloatRange(from=-1.0F, to=1.0f) public abstract float getCapacity();
+    method public abstract int getStatus();
+    method public abstract boolean hasBattery();
+    field public static final int STATUS_CHARGING = 2; // 0x2
+    field public static final int STATUS_DISCHARGING = 3; // 0x3
+    field public static final int STATUS_FULL = 5; // 0x5
+    field public static final int STATUS_NOT_CHARGING = 4; // 0x4
+    field public static final int STATUS_UNKNOWN = 1; // 0x1
+  }
+
   @Deprecated public class Camera {
     method @Deprecated public final void addCallbackBuffer(byte[]);
     method @Deprecated public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
@@ -31847,6 +31864,10 @@
   public final class ImplicitDirectBootViolation extends android.os.strictmode.Violation {
   }
 
+  public final class IncorrectContextUseViolation extends android.os.strictmode.Violation {
+    ctor public IncorrectContextUseViolation(@NonNull String, @NonNull Throwable);
+  }
+
   public class InstanceCountViolation extends android.os.strictmode.Violation {
     method public long getNumberOfInstances();
   }
@@ -46360,6 +46381,7 @@
 
   public final class InputDevice implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.hardware.Battery getBattery();
     method public int getControllerNumber();
     method public String getDescriptor();
     method public static android.view.InputDevice getDevice(int);
@@ -46414,6 +46436,7 @@
     field public static final int SOURCE_MOUSE = 8194; // 0x2002
     field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
     field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+    field public static final int SOURCE_SENSOR = 67108864; // 0x4000000
     field public static final int SOURCE_STYLUS = 16386; // 0x4002
     field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
     field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -53055,6 +53078,10 @@
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int);
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int, int);
+    method @Deprecated public void setDial(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setHourHand(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setMinuteHand(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setSecondHand(@Nullable android.graphics.drawable.Icon);
   }
 
   public class ArrayAdapter<T> extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0f49dc5..e8650fa 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -157,6 +157,10 @@
 
 package android.net {
 
+  public final class ConnectivityFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
   public class ConnectivityManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 845bb3d..683bc84 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@
     field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
     field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
     field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+    field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
     field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
     field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
     field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
@@ -1493,6 +1494,169 @@
 
 }
 
+package android.app.smartspace {
+
+  public final class SmartspaceAction implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public CharSequence getContentDescription();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public android.graphics.drawable.Icon getIcon();
+    method @NonNull public String getId();
+    method @Nullable public android.content.Intent getIntent();
+    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @Nullable public CharSequence getSubtitle();
+    method @NonNull public CharSequence getTitle();
+    method @Nullable public android.os.UserHandle getUserHandle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceAction> CREATOR;
+  }
+
+  public static final class SmartspaceAction.Builder {
+    ctor public SmartspaceAction.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceAction build();
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setContentDescription(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIntent(@Nullable android.content.Intent);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setSubtitle(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+  }
+
+  public final class SmartspaceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public String getPackageName();
+    method @NonNull public int getSmartspaceTargetCount();
+    method @NonNull public String getUiSurface();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceConfig> CREATOR;
+  }
+
+  public static final class SmartspaceConfig.Builder {
+    ctor public SmartspaceConfig.Builder(@NonNull android.content.Context, @NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceConfig build();
+    method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setSmartspaceTargetCount(int);
+  }
+
+  public final class SmartspaceManager {
+    method @NonNull public android.app.smartspace.SmartspaceSession createSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig);
+  }
+
+  public final class SmartspaceSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public void destroy();
+    method protected void finalize();
+    method public void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceTargetEvent);
+    method public void registerSmartspaceUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.smartspace.SmartspaceSession.Callback);
+    method public void requestSmartspaceUpdate();
+    method public void unregisterSmartspaceUpdates(@NonNull android.app.smartspace.SmartspaceSession.Callback);
+  }
+
+  public static interface SmartspaceSession.Callback {
+    method public void onTargetsAvailable(@NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+  }
+
+  public final class SmartspaceSessionId implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getId();
+    method @NonNull public int getUserId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceSessionId> CREATOR;
+  }
+
+  public final class SmartspaceTarget implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getActionChips();
+    method @Nullable public String getAssociatedSmartspaceTargetId();
+    method @Nullable public android.app.smartspace.SmartspaceAction getBaseAction();
+    method @NonNull public android.content.ComponentName getComponentName();
+    method @NonNull public long getCreationTimeMillis();
+    method @NonNull public long getExpiryTimeMillis();
+    method @NonNull public int getFeatureType();
+    method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
+    method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+    method @NonNull public float getScore();
+    method @Nullable public android.net.Uri getSliceUri();
+    method @NonNull public String getSmartspaceTargetId();
+    method @Nullable public String getSourceNotificationKey();
+    method @NonNull public android.os.UserHandle getUserHandle();
+    method @Nullable public android.appwidget.AppWidgetProviderInfo getWidgetId();
+    method @NonNull public boolean isSensitive();
+    method @NonNull public boolean shouldShowExpanded();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTarget> CREATOR;
+    field public static final int FEATURE_ALARM = 7; // 0x7
+    field public static final int FEATURE_BEDTIME_ROUTINE = 16; // 0x10
+    field public static final int FEATURE_CALENDAR = 2; // 0x2
+    field public static final int FEATURE_COMMUTE_TIME = 3; // 0x3
+    field public static final int FEATURE_CONSENT = 11; // 0xb
+    field public static final int FEATURE_ETA_MONITORING = 18; // 0x12
+    field public static final int FEATURE_FITNESS_TRACKING = 17; // 0x11
+    field public static final int FEATURE_FLIGHT = 4; // 0x4
+    field public static final int FEATURE_LOYALTY_CARD = 14; // 0xe
+    field public static final int FEATURE_MEDIA = 15; // 0xf
+    field public static final int FEATURE_MISSED_CALL = 19; // 0x13
+    field public static final int FEATURE_ONBOARDING = 8; // 0x8
+    field public static final int FEATURE_PACKAGE_TRACKING = 20; // 0x14
+    field public static final int FEATURE_REMINDER = 6; // 0x6
+    field public static final int FEATURE_SHOPPING_LIST = 13; // 0xd
+    field public static final int FEATURE_SPORTS = 9; // 0x9
+    field public static final int FEATURE_STOCK_PRICE_CHANGE = 12; // 0xc
+    field public static final int FEATURE_STOPWATCH = 22; // 0x16
+    field public static final int FEATURE_TIMER = 21; // 0x15
+    field public static final int FEATURE_TIPS = 5; // 0x5
+    field public static final int FEATURE_UNDEFINED = 0; // 0x0
+    field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
+    field public static final int FEATURE_WEATHER = 1; // 0x1
+    field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+  }
+
+  public static final class SmartspaceTarget.Builder {
+    ctor public SmartspaceTarget.Builder(@NonNull String, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+    method @NonNull public android.app.smartspace.SmartspaceTarget build();
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setActionChips(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setAssociatedSmartspaceTargetId(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setBaseAction(@NonNull android.app.smartspace.SmartspaceAction);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setCreationTimeMillis(@NonNull long);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setExpiryTimeMillis(@NonNull long);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(@NonNull int);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(@NonNull float);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(@NonNull boolean);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(@NonNull boolean);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidgetId(@NonNull android.appwidget.AppWidgetProviderInfo);
+  }
+
+  public final class SmartspaceTargetEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int getEventType();
+    method @Nullable public String getSmartspaceActionId();
+    method @Nullable public android.app.smartspace.SmartspaceTarget getSmartspaceTarget();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTargetEvent> CREATOR;
+    field public static final int EVENT_TARGET_BLOCK = 5; // 0x5
+    field public static final int EVENT_TARGET_DISMISS = 4; // 0x4
+    field public static final int EVENT_TARGET_INTERACTION = 1; // 0x1
+    field public static final int EVENT_TARGET_IN_VIEW = 2; // 0x2
+    field public static final int EVENT_TARGET_OUT_OF_VIEW = 3; // 0x3
+    field public static final int EVENT_UI_SURFACE_IN_VIEW = 6; // 0x6
+    field public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7; // 0x7
+  }
+
+  public static final class SmartspaceTargetEvent.Builder {
+    ctor public SmartspaceTargetEvent.Builder(int);
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent build();
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceActionId(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceTarget(@NonNull android.app.smartspace.SmartspaceTarget);
+  }
+
+}
+
 package android.app.time {
 
   public final class TimeManager {
@@ -1955,6 +2119,7 @@
     field public static final String ROLLBACK_SERVICE = "rollback";
     field public static final String SEARCH_UI_SERVICE = "search_ui";
     field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
+    field public static final String SMARTSPACE_SERVICE = "smartspace";
     field public static final String STATS_MANAGER = "stats";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -3183,6 +3348,7 @@
 
   public class ContextHubClientCallback {
     ctor public ContextHubClientCallback();
+    method public void onClientAuthorizationChanged(@NonNull android.hardware.location.ContextHubClient, long, int);
     method public void onHubReset(android.hardware.location.ContextHubClient);
     method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage);
     method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int);
@@ -3219,6 +3385,7 @@
 
   public class ContextHubIntentEvent {
     method @NonNull public static android.hardware.location.ContextHubIntentEvent fromIntent(@NonNull android.content.Intent);
+    method public int getClientAuthorizationState();
     method @NonNull public android.hardware.location.ContextHubInfo getContextHubInfo();
     method public int getEventType();
     method public int getNanoAppAbortCode();
@@ -3227,8 +3394,10 @@
   }
 
   public final class ContextHubManager {
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.location.ContextHubClientCallback);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback, @NonNull java.util.concurrent.Executor);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
@@ -3246,6 +3415,10 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public int unloadNanoApp(int);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+    field public static final int AUTHORIZATION_DENIED = 0; // 0x0
+    field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
+    field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
+    field public static final int EVENT_CLIENT_AUTHORIZATION = 7; // 0x7
     field public static final int EVENT_HUB_RESET = 6; // 0x6
     field public static final int EVENT_NANOAPP_ABORTED = 4; // 0x4
     field public static final int EVENT_NANOAPP_DISABLED = 3; // 0x3
@@ -3253,6 +3426,7 @@
     field public static final int EVENT_NANOAPP_LOADED = 0; // 0x0
     field public static final int EVENT_NANOAPP_MESSAGE = 5; // 0x5
     field public static final int EVENT_NANOAPP_UNLOADED = 1; // 0x1
+    field public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
     field public static final String EXTRA_CONTEXT_HUB_INFO = "android.hardware.location.extra.CONTEXT_HUB_INFO";
     field public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE";
     field public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
@@ -3491,8 +3665,10 @@
 
   public final class NanoAppState implements android.os.Parcelable {
     ctor public NanoAppState(long, int, boolean);
+    ctor public NanoAppState(long, int, boolean, @NonNull java.util.List<java.lang.String>);
     method public int describeContents();
     method public long getNanoAppId();
+    method @NonNull public java.util.List<java.lang.String> getNanoAppPermissions();
     method public long getNanoAppVersion();
     method public boolean isEnabled();
     method public void writeToParcel(android.os.Parcel, int);
@@ -4635,6 +4811,7 @@
   public static class AudioAttributes.Builder {
     method public android.media.AudioAttributes.Builder addBundle(@NonNull android.os.Bundle);
     method public android.media.AudioAttributes.Builder setCapturePreset(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordMode();
     method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
   }
@@ -7105,6 +7282,7 @@
     method @Nullable public String getSsid();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+    field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
     field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
     field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
     field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
@@ -9851,6 +10029,7 @@
     method public void onNotificationDirectReplied(@NonNull String);
     method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
+    method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel, @NonNull android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
     method public void onNotificationVisibilityChanged(@NonNull String, boolean);
@@ -10076,6 +10255,21 @@
 
 }
 
+package android.service.smartspace {
+
+  public abstract class SmartspaceService extends android.app.Service {
+    ctor public SmartspaceService();
+    method @MainThread public abstract void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull android.app.smartspace.SmartspaceTargetEvent);
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onCreateSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig, @NonNull android.app.smartspace.SmartspaceSessionId);
+    method @MainThread public abstract void onDestroy(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method public abstract void onDestroySmartspaceSession(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method @MainThread public abstract void onRequestSmartspaceUpdate(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method public final void updateSmartspaceTargets(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+  }
+
+}
+
 package android.service.storage {
 
   public abstract class ExternalStorageService extends android.app.Service {
@@ -13160,6 +13354,7 @@
     field public static final String IPTYPE_IPV6 = "IPV6";
     field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string";
     field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string";
+    field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string";
     field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string";
     field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string";
     field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string";
@@ -13192,6 +13387,7 @@
     field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int";
     field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string";
     field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string";
+    field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string";
     field public static final String SIP_TRANSPORT_TCP = "TCP";
     field public static final String SIP_TRANSPORT_UDP = "UDP";
   }
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8b3cee4..58f6c52 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -6,9 +6,11 @@
 BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
 
+
 ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
     Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
-    
+
+
 GenericException: android.app.prediction.AppPredictor#finalize():
     
 GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -21,6 +23,8 @@
 
 IntentBuilderName: android.app.search.SearchAction#getIntent():
     
+IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
+    Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
 
 
 KotlinKeyword: android.app.Notification#when:
@@ -83,6 +87,10 @@
     
 
 
+OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
+    Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
+
+
 ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
     
 ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -187,11 +195,10 @@
     
 SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
-SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
-    SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-    
 SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
+SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
 SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -258,3 +265,5 @@
     Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
 UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
     
+UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
+    Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bc1858b..e0391ee 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -401,10 +401,10 @@
     field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
     field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
     field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
-    field public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
+    field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
     field public static final int CODE_OK = 0; // 0x0
     field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
-    field public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
+    field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
     field public static final int CODE_SYSTEM_USER = 10; // 0xa
     field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
     field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
@@ -2324,6 +2324,8 @@
   }
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
+    method @Nullable public final android.os.IBinder getWindowContextToken();
+    method public final void setWindowContextToken(@NonNull android.os.IBinder);
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
     field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
     field public CharSequence accessibilityTitle;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9b141b7..1f9cb64 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2291,9 +2291,10 @@
      * Resources if one has already been created.
      */
     Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
-            String[] libDirs, LoadedApk pkgInfo) {
+            String[] libDirs, LoadedApk pkgInfo, Configuration overrideConfig) {
         return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
-                null, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
+                null, overrideConfig, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(),
+                null);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d716a3c..7410a1c 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -50,7 +50,7 @@
  *
  * <p>
  * Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
- * when it was killed by the ystem because it was running low on memory. Reason
+ * when it was killed by the system because it was running low on memory. Reason
  * of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
  * auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
  * additional diagnostic information.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 77542bd..7e7f887 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -70,6 +70,7 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
@@ -1691,20 +1692,29 @@
     @Override
     public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
             throws NameNotFoundException {
+        return getResourcesForApplication(app, null);
+    }
+
+    @Override
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app,
+            @Nullable Configuration configuration) throws NameNotFoundException {
         if (app.packageName.equals("system")) {
-            return mContext.mMainThread.getSystemUiContext().getResources();
+            Context sysuiContext = mContext.mMainThread.getSystemUiContext();
+            if (configuration != null) {
+                sysuiContext = sysuiContext.createConfigurationContext(configuration);
+            }
+            return sysuiContext.getResources();
         }
         final boolean sameUid = (app.uid == Process.myUid());
         final Resources r = mContext.mMainThread.getTopLevelResources(
-                    sameUid ? app.sourceDir : app.publicSourceDir,
-                    sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
-                    app.resourceDirs, app.sharedLibraryFiles,
-                    mContext.mPackageInfo);
+                sameUid ? app.sourceDir : app.publicSourceDir,
+                sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
+                app.resourceDirs, app.sharedLibraryFiles,
+                mContext.mPackageInfo, configuration);
         if (r != null) {
             return r;
         }
         throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
-
     }
 
     @Override
diff --git a/graphics/java/android/graphics/GameManager.java b/core/java/android/app/GameManager.java
similarity index 98%
rename from graphics/java/android/graphics/GameManager.java
rename to core/java/android/app/GameManager.java
index a58aeb4..8b6570f 100644
--- a/graphics/java/android/graphics/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.graphics;
+package android.app;
 
 import android.annotation.IntDef;
 import android.annotation.SystemService;
diff --git a/graphics/java/android/graphics/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
similarity index 96%
rename from graphics/java/android/graphics/IGameManagerService.aidl
rename to core/java/android/app/IGameManagerService.aidl
index 7d3a4fb..c8e1478 100644
--- a/graphics/java/android/graphics/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -14,7 +14,7 @@
 ** limitations under the License.
 */
 
-package android.graphics;
+package android.app;
 
 /**
  * @hide
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 534f3e2..671315f 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemApi.Client;
 import android.annotation.TestApi;
@@ -1275,6 +1276,7 @@
      * @param flags MATCH_* flags from {@link android.content.pm.PackageManager}.
      * @hide
      */
+    @SuppressLint("NullableCollection")
     @RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
     @SystemApi(client = Client.MODULE_LIBRARIES)
     @TestApi
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 1498dae7..f8c33b5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
 import android.app.role.RoleFrameworkInitializer;
 import android.app.search.SearchUiManager;
 import android.app.slice.SliceManager;
+import android.app.smartspace.SmartspaceManager;
 import android.app.time.TimeManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timedetector.TimeDetectorImpl;
@@ -72,7 +73,6 @@
 import android.content.rollback.RollbackManagerFrameworkInitializer;
 import android.debug.AdbManager;
 import android.debug.IAdbManager;
-import android.graphics.GameManager;
 import android.graphics.fonts.FontManager;
 import android.hardware.ConsumerIrManager;
 import android.hardware.ISerialManager;
@@ -120,21 +120,16 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityManager;
+import android.net.ConnectivityFrameworkInitializer;
 import android.net.EthernetManager;
-import android.net.IConnectivityManager;
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
-import android.net.ITestNetworkManager;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
 import android.net.NetworkWatchlistManager;
-import android.net.TestNetworkManager;
 import android.net.TetheringManager;
-import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -163,7 +158,6 @@
 import android.os.IncidentManager;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StatsFrameworkInitializer;
@@ -370,15 +364,6 @@
         // (which extends it).
         SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
 
-        registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
-                new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
-            @Override
-            public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new ConnectivityManager(context, service);
-            }});
-
         registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
             @Override
             public IBinder createService() throws ServiceNotFoundException {
@@ -412,50 +397,6 @@
                 return new IpSecManager(ctx, service);
             }});
 
-        registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
-                new CachedServiceFetcher<VpnManager>() {
-            @Override
-            public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new VpnManager(ctx, service);
-            }});
-
-        registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
-                ConnectivityDiagnosticsManager.class,
-                new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
-            @Override
-            public ConnectivityDiagnosticsManager createService(ContextImpl ctx)
-                    throws ServiceNotFoundException {
-                // ConnectivityDiagnosticsManager is backed by ConnectivityService
-                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new ConnectivityDiagnosticsManager(ctx, service);
-            }});
-
-        registerService(
-                Context.TEST_NETWORK_SERVICE,
-                TestNetworkManager.class,
-                new StaticApplicationContextServiceFetcher<TestNetworkManager>() {
-                    @Override
-                    public TestNetworkManager createService(Context context)
-                            throws ServiceNotFoundException {
-                        IBinder csBinder =
-                                ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                        IConnectivityManager csMgr =
-                                IConnectivityManager.Stub.asInterface(csBinder);
-
-                        final IBinder tnBinder;
-                        try {
-                            tnBinder = csMgr.startOrGetTestNetworkService();
-                        } catch (RemoteException e) {
-                            throw new ServiceNotFoundException(Context.TEST_NETWORK_SERVICE);
-                        }
-                        ITestNetworkManager tnMgr = ITestNetworkManager.Stub.asInterface(tnBinder);
-                        return new TestNetworkManager(tnMgr);
-                    }
-                });
-
         registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
                 new StaticServiceFetcher<CountryDetector>() {
             @Override
@@ -1224,6 +1165,16 @@
                 }
             });
 
+        registerService(Context.SMARTSPACE_SERVICE, SmartspaceManager.class,
+            new CachedServiceFetcher<SmartspaceManager>() {
+                @Override
+                public SmartspaceManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                    IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+                    return b == null ? null : new SmartspaceManager(ctx);
+                }
+            });
+
         registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
                 new CachedServiceFetcher<AppPredictionManager>() {
             @Override
@@ -1441,6 +1392,7 @@
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
             // modules.
+            ConnectivityFrameworkInitializer.registerServiceWrappers();
             JobSchedulerFrameworkInitializer.registerServiceWrappers();
             BlobStoreManagerFrameworkInitializer.initialize();
             TelephonyFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ada703b..e84d4a5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -453,59 +453,6 @@
             "android.app.action.PROVISION_FINANCED_DEVICE";
 
     /**
-     * Activity action: Starts the provisioning flow which sets up a managed device.
-     * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
-     *
-     * <p>NOTE: This is only supported on split system user devices, and puts the device into a
-     * management state that is distinct from that reached by
-     * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
-     * user, and only has control over device-wide policies, not individual users and their data.
-     * The primary benefit is that multiple non-system users are supported when provisioning using
-     * this form of device management.
-     *
-     * <p>During device owner provisioning a device admin app is set as the owner of the device.
-     * A device owner has full control over the device. The device owner can not be modified by the
-     * user.
-     *
-     * <p>A typical use case would be a device that is owned by a company, but used by either an
-     * employee or client.
-     *
-     * <p>An intent with this action can be sent only on an unprovisioned device.
-     * It is possible to check if provisioning is allowed or not by querying the method
-     * {@link #isProvisioningAllowed(String)}.
-     *
-     * <p>The intent contains the following extras:
-     * <ul>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
-     * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
-     * </ul>
-     *
-     * <p>When device owner provisioning has completed, an intent of the type
-     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
-     * device owner.
-     *
-     * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
-     * completed, along with the above broadcast, activity intent
-     * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
-     *
-     * <p>If provisioning fails, the device is factory reset.
-     *
-     * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
-     * of the provisioning flow was successful, although this doesn't guarantee the full flow will
-     * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
-     * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
-        = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
-
-    /**
      * Activity action: Finalizes management provisioning, should be used after user-setup
      * has been completed and {@link #getUserProvisioningState()} returns one of:
      * <ul>
@@ -1990,8 +1937,8 @@
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when provisioning is allowed.
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
+     * when provisioning is allowed.
      *
      * @hide
      */
@@ -2001,9 +1948,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device
-     * owner.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a
+     * device owner.
      *
      * @hide
      */
@@ -2013,9 +1959,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner
+     *  and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
      *
      * @hide
      */
@@ -2025,8 +1970,7 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running.
      *
      * @hide
      */
@@ -2036,9 +1980,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and
-     * for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been
+     * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
      *
      * @hide
      */
@@ -2064,8 +2007,7 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user.
      *
      * @hide
      */
@@ -2075,9 +2017,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER}
-     * when the device is a watch and is already paired.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
+     * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired.
      *
      * @hide
      */
@@ -2121,14 +2062,11 @@
 
     /**
      * TODO (b/137101239): clean up split system user codes
-     * Result code for {@link #checkProvisioningPreCondition}.
-     *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system
-     * user.
      *
      * @hide
-     */
+     * @deprecated not used anymore but can't be removed since it's a @TestApi.
+     **/
+    @Deprecated
     @TestApi
     public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
 
@@ -2136,8 +2074,7 @@
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do not support device
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device
      * admins.
      *
      * @hide
@@ -2149,11 +2086,10 @@
      * TODO (b/137101239): clean up split system user codes
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
-     * system user on a split system user device.
-     *
      * @hide
+     * @deprecated not used anymore but can't be removed since it's a @TestApi.
      */
+    @Deprecated
     @TestApi
     public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
 
@@ -7147,17 +7083,9 @@
     }
 
     /**
+     * TODO (b/137101239): remove this method in follow-up CL
+     * since it's only used for split system user.
      * Called by a device owner to set whether all users created on the device should be ephemeral.
-     * <p>
-     * The system user is exempt from this policy - it is never ephemeral.
-     * <p>
-     * The calling device admin must be the device owner. If it is not, a security exception will be
-     * thrown.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param forceEphemeralUsers If true, all the existing users will be deleted and all
-     *            subsequently created users will be ephemeral.
-     * @throws SecurityException if {@code admin} is not a device owner.
      * @hide
      */
     public void setForceEphemeralUsers(
@@ -7173,6 +7101,8 @@
     }
 
     /**
+     * TODO (b/137101239): remove this method in follow-up CL
+     * since it's only used for split system user.
      * @return true if all users are created ephemeral.
      * @throws SecurityException if {@code admin} is not a device owner.
      * @hide
@@ -10731,9 +10661,7 @@
      * profile or user, setting the given package as owner.
      *
      * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     *        {@link #ACTION_PROVISION_MANAGED_PROFILE},
-     *        {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE},
-     *        {@link #ACTION_PROVISION_MANAGED_USER}
+     *        {@link #ACTION_PROVISION_MANAGED_PROFILE}
      * @param packageName The package of the component that would be set as device, user, or profile
      *        owner.
      * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 65a2164..2b52875 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2,6 +2,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -1542,6 +1543,7 @@
          * {@link View#getOnReceiveContentMimeTypes()} for details.
          */
         @Nullable
+        @SuppressLint("NullableCollection")
         public String[] getOnReceiveContentMimeTypes() {
             return mOnReceiveContentMimeTypes;
         }
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index 447ca31..3ab20bb 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -46,6 +47,7 @@
 
     public Query(@NonNull String input,
             long timestamp,
+            @SuppressLint("NullableCollection")
             @Nullable Bundle extras) {
         mInput = input;
         mTimestamp = timestamp;
@@ -69,6 +71,7 @@
     }
 
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
diff --git a/core/java/android/app/search/SearchAction.java b/core/java/android/app/search/SearchAction.java
index a76154a..9e40e7e 100644
--- a/core/java/android/app/search/SearchAction.java
+++ b/core/java/android/app/search/SearchAction.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Intent;
@@ -167,6 +168,7 @@
     /**
      * Returns the extra bundle for this object.
      */
+    @SuppressLint("NullableCollection")
     public @Nullable Bundle getExtras() {
         return mExtras;
     }
@@ -325,7 +327,8 @@
          * Sets the extra.
          */
         @NonNull
-        public SearchAction.Builder setExtras(@Nullable Bundle extras) {
+        public SearchAction.Builder setExtras(
+                @SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/core/java/android/app/search/SearchContext.java b/core/java/android/app/search/SearchContext.java
index 9bf766d..548b7da 100644
--- a/core/java/android/app/search/SearchContext.java
+++ b/core/java/android/app/search/SearchContext.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -52,7 +53,7 @@
 
     public SearchContext(int resultTypes,
             int queryTimeoutMillis,
-            @Nullable Bundle extras) {
+            @SuppressLint("NullableCollection") @Nullable Bundle extras) {
         mResultTypes = resultTypes;
         mTimeoutMillis = queryTimeoutMillis;
         mExtras = extras;
@@ -83,6 +84,7 @@
     }
 
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index cac22d81..6a80f8b 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.pm.ShortcutInfo;
@@ -224,6 +225,7 @@
      * Return extra bundle.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
@@ -386,7 +388,7 @@
          * TODO: add comment
          */
         @NonNull
-        public Builder setExtras(@Nullable Bundle extras) {
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/core/java/android/app/smartspace/ISmartspaceCallback.aidl b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
new file mode 100644
index 0000000..df105f9
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.smartspace;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface ISmartspaceCallback {
+
+    void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/smartspace/ISmartspaceManager.aidl b/core/java/android/app/smartspace/ISmartspaceManager.aidl
new file mode 100644
index 0000000..e7ec889
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceManager.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface ISmartspaceManager {
+
+    void createSmartspaceSession(in SmartspaceConfig config, in SmartspaceSessionId sessionId,
+            in IBinder token);
+
+    void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+    void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+    void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void destroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/app/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
new file mode 100644
index 0000000..033cda1
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -0,0 +1,353 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceAction} represents an action which can be taken by a user by tapping on either
+ * the title, the subtitle or on the icon. Supported instances are Intents, PendingIntents or a
+ * ShortcutInfo (by putting the ShortcutInfoId in the bundle). These actions can be called from
+ * another process or within the client process.
+ *
+ * Clients can also receive conditional Intents/PendingIntents in the extras bundle which are
+ * supposed to be fired when the conditions are met. For example, a user can invoke a dismiss/block
+ * action on a game score card but the intention is to only block the team and not the entire
+ * feature.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceAction implements Parcelable {
+
+    private static final String TAG = "SmartspaceAction";
+
+    /** A unique Id of this {@link SmartspaceAction}. */
+    @NonNull
+    private final String mId;
+
+    /** An Icon which can be displayed in the UI. */
+    @Nullable
+    private final Icon mIcon;
+
+    /** Title associated with an action. */
+    @NonNull
+    private final CharSequence mTitle;
+
+    /** Subtitle associated with an action. */
+    @Nullable
+    private final CharSequence mSubtitle;
+
+    @Nullable
+    private final CharSequence mContentDescription;
+
+    @Nullable
+    private final PendingIntent mPendingIntent;
+
+    @Nullable
+    private final Intent mIntent;
+
+    @Nullable
+    private final UserHandle mUserHandle;
+
+    @Nullable
+    private Bundle mExtras;
+
+    SmartspaceAction(Parcel in) {
+        mId = in.readString();
+        mIcon = in.readTypedObject(Icon.CREATOR);
+        mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mIntent = in.readTypedObject(Intent.CREATOR);
+        mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+        mExtras = in.readTypedObject(Bundle.CREATOR);
+    }
+
+    private SmartspaceAction(
+            @NonNull String id,
+            @Nullable Icon icon,
+            @NonNull CharSequence title,
+            @Nullable CharSequence subtitle,
+            @Nullable CharSequence contentDescription,
+            @Nullable PendingIntent pendingIntent,
+            @Nullable Intent intent,
+            @Nullable UserHandle userHandle,
+            @Nullable Bundle extras) {
+        mId = Objects.requireNonNull(id);
+        mIcon = icon;
+        mTitle = Objects.requireNonNull(title);
+        mSubtitle = subtitle;
+        mContentDescription = contentDescription;
+        mPendingIntent = pendingIntent;
+        mIntent = intent;
+        mUserHandle = userHandle;
+        mExtras = extras;
+    }
+
+    /**
+     * Returns the unique id of this object.
+     */
+    public @NonNull String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns an icon representing the action.
+     */
+    public @Nullable Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns a title representing the action.
+     */
+    public @NonNull CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns a subtitle representing the action.
+     */
+    public @Nullable CharSequence getSubtitle() {
+        return mSubtitle;
+    }
+
+    /**
+     * Returns a content description representing the action.
+     */
+    public @Nullable CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Returns the action intent.
+     */
+    public @Nullable PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /**
+     * Returns the intent.
+     */
+    public @Nullable Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Returns the user handle.
+     */
+    public @Nullable UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
+     * Returns the extra bundle for this object.
+     */
+    public @Nullable Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceAction)) return false;
+        SmartspaceAction that = (SmartspaceAction) o;
+        return mId.equals(that.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mId);
+        out.writeTypedObject(mIcon, flags);
+        TextUtils.writeToParcel(mTitle, out, flags);
+        TextUtils.writeToParcel(mSubtitle, out, flags);
+        TextUtils.writeToParcel(mContentDescription, out, flags);
+        out.writeTypedObject(mPendingIntent, flags);
+        out.writeTypedObject(mIntent, flags);
+        out.writeTypedObject(mUserHandle, flags);
+        out.writeBundle(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceAction{"
+                + "mId='" + mId + '\''
+                + ", mIcon=" + mIcon
+                + ", mTitle=" + mTitle
+                + ", mSubtitle=" + mSubtitle
+                + ", mContentDescription=" + mContentDescription
+                + ", mPendingIntent=" + mPendingIntent
+                + ", mIntent=" + mIntent
+                + ", mUserHandle=" + mUserHandle
+                + ", mExtras=" + mExtras
+                + '}';
+    }
+
+    public static final @NonNull Creator<SmartspaceAction> CREATOR =
+            new Creator<SmartspaceAction>() {
+                public SmartspaceAction createFromParcel(Parcel in) {
+                    return new SmartspaceAction(in);
+                }
+                public SmartspaceAction[] newArray(int size) {
+                    return new SmartspaceAction[size];
+                }
+            };
+
+    /**
+     * A builder for Smartspace action object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @NonNull
+        private String mId;
+
+        @Nullable
+        private Icon mIcon;
+
+        @NonNull
+        private CharSequence mTitle;
+
+        @Nullable
+        private CharSequence mSubtitle;
+
+        @Nullable
+        private CharSequence mContentDescription;
+
+        @Nullable
+        private PendingIntent mPendingIntent;
+
+        @Nullable
+        private Intent mIntent;
+
+        @Nullable
+        private UserHandle mUserHandle;
+
+        @Nullable
+        private Bundle mExtras;
+
+        /**
+         * Id and title are required.
+         */
+        public Builder(@NonNull String id, @NonNull String title) {
+            mId = Objects.requireNonNull(id);
+            mTitle = Objects.requireNonNull(title);
+        }
+
+        /**
+         * Sets the icon.
+         */
+        @NonNull
+        public Builder setIcon(
+                @Nullable Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the subtitle.
+         */
+        @NonNull
+        public Builder setSubtitle(
+                @Nullable CharSequence subtitle) {
+            mSubtitle = subtitle;
+            return this;
+        }
+
+        /**
+         * Sets the content description.
+         */
+        @NonNull
+        public Builder setContentDescription(
+                @Nullable CharSequence contentDescription) {
+            mContentDescription = contentDescription;
+            return this;
+        }
+
+        /**
+         * Sets the pending intent.
+         */
+        @NonNull
+        public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Sets the user handle.
+         */
+        @NonNull
+        public Builder setUserHandle(@Nullable UserHandle userHandle) {
+            mUserHandle = userHandle;
+            return this;
+        }
+
+        /**
+         * Sets the intent.
+         */
+        @NonNull
+        public Builder setIntent(@Nullable Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the extra.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceAction instance.
+         *
+         * @throws IllegalStateException if no target is set
+         */
+        @NonNull
+        public SmartspaceAction build() {
+            return new SmartspaceAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
+                    mPendingIntent, mIntent, mUserHandle, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.aidl b/core/java/android/app/smartspace/SmartspaceConfig.aidl
new file mode 100644
index 0000000..136b6f4
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+parcelable SmartspaceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.java b/core/java/android/app/smartspace/SmartspaceConfig.java
new file mode 100644
index 0000000..1f9cbb5
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.java
@@ -0,0 +1,202 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceConfig} instance is supposed to be created by a smartspace client for each
+ * UISurface. The client can specify some initialization conditions for the UISurface like its name,
+ * expected number of smartspace cards etc. The clients can also specify if they want periodic
+ * updates or their desired maximum refresh frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceConfig implements Parcelable {
+
+    /**
+     * The least number of smartspace targets expected to be predicted by the backend. The backend
+     * will always try to satisfy this threshold but it is not guaranteed to always meet it.
+     */
+    private final int mSmartspaceTargetCount;
+
+    /**
+     * A {@link mUiSurface} is the name of the surface which will be used to display the cards. A
+     * few examples are homescreen, lockscreen, aod etc.
+     */
+    @NonNull
+    private final String mUiSurface;
+
+    /** Package name of the client. */
+    @NonNull
+    private String mPackageName;
+
+    /** Send other client UI configurations in extras.
+     *
+     * This can include:
+     *
+     *  - Desired maximum update frequency
+     *  - Request to get periodic updates
+     *  - Request to support multiple clients for the same UISurface.
+     */
+    @Nullable
+    private final Bundle mExtras;
+
+    private SmartspaceConfig(@NonNull String uiSurface, int numPredictedTargets,
+            @NonNull String packageName, @Nullable Bundle extras) {
+        mUiSurface = uiSurface;
+        mSmartspaceTargetCount = numPredictedTargets;
+        mPackageName = packageName;
+        mExtras = extras;
+    }
+
+    private SmartspaceConfig(Parcel parcel) {
+        mUiSurface = parcel.readString();
+        mSmartspaceTargetCount = parcel.readInt();
+        mPackageName = parcel.readString();
+        mExtras = parcel.readBundle();
+    }
+
+    /** Returns the package name of the prediction context. */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns the number of smartspace targets requested by the user. */
+    @NonNull
+    public int getSmartspaceTargetCount() {
+        return mSmartspaceTargetCount;
+    }
+
+    /** Returns the UISurface requested by the client. */
+    @NonNull
+    public String getUiSurface() {
+        return mUiSurface;
+    }
+
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mUiSurface);
+        dest.writeInt(mSmartspaceTargetCount);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mExtras);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SmartspaceConfig that = (SmartspaceConfig) o;
+        return mSmartspaceTargetCount == that.mSmartspaceTargetCount
+                && Objects.equals(mUiSurface, that.mUiSurface)
+                && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mExtras, that.mExtras);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSmartspaceTargetCount, mUiSurface, mPackageName, mExtras);
+    }
+
+    /**
+     * @see Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceConfig> CREATOR =
+            new Creator<SmartspaceConfig>() {
+                public SmartspaceConfig createFromParcel(Parcel parcel) {
+                    return new SmartspaceConfig(parcel);
+                }
+
+                public SmartspaceConfig[] newArray(int size) {
+                    return new SmartspaceConfig[size];
+                }
+            };
+
+    /**
+     * A builder for {@link SmartspaceConfig}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @NonNull
+        private int mSmartspaceTargetCount = 5; // Default count is 5
+        @NonNull
+        private final String mUiSurface;
+        @NonNull
+        private final String mPackageName;
+        @NonNull
+        private Bundle mExtras = Bundle.EMPTY;
+
+        /**
+         * @param context The {@link Context} which is used to fetch the package name.
+         * @param uiSurface the UI Surface name associated with this context.
+         * @hide
+         */
+        @SystemApi
+        public Builder(@NonNull Context context, @NonNull String uiSurface) {
+            mPackageName = context.getPackageName();
+            this.mUiSurface = uiSurface;
+        }
+
+        /**
+         * Used to set the expected number of cards for this context.
+         */
+        @NonNull
+        public Builder setSmartspaceTargetCount(int smartspaceTargetCount) {
+            this.mSmartspaceTargetCount = smartspaceTargetCount;
+            return this;
+        }
+
+        /**
+         * Used to send a bundle containing extras for the {@link SmartspaceConfig}.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            this.mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Returns an instance of {@link SmartspaceConfig}.
+         */
+        @NonNull
+        public SmartspaceConfig build() {
+            return new SmartspaceConfig(mUiSurface, mSmartspaceTargetCount, mPackageName, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceManager.java b/core/java/android/app/smartspace/SmartspaceManager.java
new file mode 100644
index 0000000..ff5eb10
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceManager.java
@@ -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.
+ */
+
+package android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import java.util.Objects;
+
+/**
+ * Smartspace is a container in Android which is used to show contextual content powered by the
+ * intelligence service running on the device. A smartspace container can be on AoD, lockscreen or
+ * on the homescreen and can show personalized cards which are either derived from on device or
+ * online signals.
+ *
+ * {@link SmartspaceManager} is a system service that provides methods to create Smartspace session
+ * clients. An instance of this class is returned when a client calls
+ * <code> context.getSystemService("smartspace"); </code>.
+ *
+ * After receiving the service, a client must call
+ * {@link SmartspaceManager#createSmartspaceSession(SmartspaceConfig)} with a corresponding
+ * {@link SmartspaceConfig} to get an instance of {@link SmartspaceSession}.
+ * This session is then a client's point of contact with the api. They can send events, request for
+ * updates using the session. It is client's duty to call {@link SmartspaceSession#destroy()} to
+ * destroy the session once they no longer need it.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceManager {
+
+    private final Context mContext;
+
+    /**
+     * @hide
+     */
+    public SmartspaceManager(Context context) {
+        mContext = Objects.requireNonNull(context);
+    }
+
+    /**
+     * Creates a new Smartspace session.
+     */
+    @NonNull
+    public SmartspaceSession createSmartspaceSession(
+            @NonNull SmartspaceConfig smartspaceConfig) {
+        return new SmartspaceSession(mContext, smartspaceConfig);
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
new file mode 100644
index 0000000..16def61
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -0,0 +1,284 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.smartspace.ISmartspaceCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Client API to share information about the Smartspace UI state and execute query.
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ *    private SmartspaceSession mSmartspaceSession;
+ *
+ *    void onCreate() {
+ *         mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig)
+ *         mSmartspaceSession.registerSmartspaceUpdates(...)
+ *    }
+ *
+ *    void onStart() {
+ *        mSmartspaceSession.requestSmartspaceUpdate()
+ *    }
+ *
+ *    void onTouch(...) OR
+ *    void onStateTransitionStarted(...) OR
+ *    void onResume(...) OR
+ *    void onStop(...) {
+ *        mSmartspaceSession.notifyEvent(event);
+ *    }
+ *
+ *    void onDestroy() {
+ *        mSmartspaceSession.unregisterPredictionUpdates()
+ *        mSmartspaceSession.destroy();
+ *    }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSession implements AutoCloseable {
+
+    private static final String TAG = SmartspaceSession.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private final android.app.smartspace.ISmartspaceManager mInterface;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+    private final SmartspaceSessionId mSessionId;
+    private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+    private final IBinder mToken = new Binder();
+
+    /**
+     * Creates a new Smartspace ui client.
+     * <p>
+     * The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
+     * no longer used.
+     *
+     * @param context          the {@link Context} of the user of this {@link SmartspaceSession}.
+     * @param smartspaceConfig the Smartspace context.
+     */
+    // b/177858121 Create weak reference child objects to not leak context.
+    SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
+        IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+        mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
+        mSessionId = new SmartspaceSessionId(
+                context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+        try {
+            mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to cerate Smartspace session", e);
+            e.rethrowFromSystemServer();
+        }
+
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Notifies the Smartspace service of a Smartspace target event.
+     *
+     * @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
+     */
+    public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+        try {
+            mInterface.notifySmartspaceEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify event", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the smartspace service for an update.
+     */
+    public void requestSmartspaceUpdate() {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+        try {
+            mInterface.requestSmartspaceUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to request update.", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the smartspace service provide continuous updates of smartspace cards via the
+     * provided callback, until the given callback is unregistered.
+     *
+     * @param callbackExecutor The callback executor to use when calling the callback.
+     * @param callback         The Callback to be called when updates of Smartspace targets are
+     *                         available.
+     */
+    public void registerSmartspaceUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        if (mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback is already registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+                    callback::onTargetsAvailable);
+            mRegisteredCallbacks.put(callback, callbackWrapper);
+            mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
+            mInterface.requestSmartspaceUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register for smartspace updates", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Requests the smartspace service to stop providing continuous updates to the provided
+     * callback until the callback is re-registered.
+     *
+     * @see {@link SmartspaceSession#registerSmartspaceUpdates(Executor, Callback)}.
+     *
+     * @param callback The callback to be unregistered.
+     */
+    public void unregisterSmartspaceUpdates(@NonNull Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        if (!mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback was never registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+            mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister for smartspace updates", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Destroys the client and unregisters the callback. Any method on this class after this call
+     * will throw {@link IllegalStateException}.
+     */
+    public void destroy() {
+        if (!mIsClosed.getAndSet(true)) {
+            mCloseGuard.close();
+
+            // Do destroy;
+            try {
+                mInterface.destroySmartspaceSession(mSessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to notify Smartspace target event", e);
+                e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            if (!mIsClosed.get()) {
+                destroy();
+            }
+        } finally {
+            try {
+                super.finalize();
+            } catch (Throwable throwable) {
+                throwable.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            finalize();
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    /**
+     * Callback for receiving smartspace updates.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new set of smartspace targets are available.
+         *
+         * @param targets Sorted list of smartspace targets.
+         */
+        void onTargetsAvailable(@NonNull List<SmartspaceTarget> targets);
+    }
+
+    static class CallbackWrapper extends Stub {
+
+        private final Consumer<List<SmartspaceTarget>> mCallback;
+        private final Executor mExecutor;
+
+        CallbackWrapper(@NonNull Executor callbackExecutor,
+                @NonNull Consumer<List<SmartspaceTarget>> callback) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onResult(ParceledListSlice result) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
+                }
+                mExecutor.execute(() -> mCallback.accept(result.getList()));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.aidl b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
new file mode 100644
index 0000000..a864412
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+parcelable SmartspaceSessionId;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.java b/core/java/android/app/smartspace/SmartspaceSessionId.java
new file mode 100644
index 0000000..5220c35
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.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 android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The id for an Smartspace session. See {@link SmartspaceSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSessionId implements Parcelable {
+
+    @NonNull
+    private final String mId;
+
+    @NonNull
+    private final int mUserId;
+
+    /**
+     * Creates a new id for a Smartspace session.
+     *
+     * @hide
+     */
+    public SmartspaceSessionId(@NonNull final String id, @NonNull final int userId) {
+        mId = id;
+        mUserId = userId;
+    }
+
+    private SmartspaceSessionId(Parcel p) {
+        mId = p.readString();
+        mUserId = p.readInt();
+    }
+
+    /**
+     * Returns a {@link String} Id of this sessionId.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the userId associated with this sessionId.
+     */
+    @NonNull
+    public int getUserId() {
+        return mUserId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        SmartspaceSessionId other = (SmartspaceSessionId) o;
+        return mId.equals(other.mId) && mUserId == other.mUserId;
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceSessionId{"
+                + "mId='" + mId + '\''
+                + ", mUserId=" + mUserId
+                + '}';
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mUserId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mUserId);
+    }
+
+    public static final @NonNull Creator<SmartspaceSessionId> CREATOR =
+            new Creator<SmartspaceSessionId>() {
+                public SmartspaceSessionId createFromParcel(Parcel parcel) {
+                    return new SmartspaceSessionId(parcel);
+                }
+
+                public SmartspaceSessionId[] newArray(int size) {
+                    return new SmartspaceSessionId[size];
+                }
+            };
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.aidl b/core/java/android/app/smartspace/SmartspaceTarget.aidl
new file mode 100644
index 0000000..3442cf2
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+parcelable SmartspaceTarget;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
new file mode 100644
index 0000000..ce5040e
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -0,0 +1,660 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTarget} is a data class which holds all properties necessary to inflate a
+ * smartspace card. It contains data and related metadata which is supposed to be utilized by
+ * smartspace clients based on their own UI/UX requirements. Some of the properties have
+ * {@link SmartspaceAction} as their type because they can have associated actions.
+ *
+ * <p><b>NOTE: </b>
+ * If {@link mWidgetId} is set, it should be preferred over all other properties.
+ * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
+ * Otherwise, the instance should be treated as a data object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTarget implements Parcelable {
+
+    /** A unique Id for an instance of {@link SmartspaceTarget}. */
+    @NonNull
+    private final String mSmartspaceTargetId;
+
+    /** A {@link SmartspaceAction} for the header in the Smartspace card. */
+    @Nullable
+    private final SmartspaceAction mHeaderAction;
+
+    /** A {@link SmartspaceAction} for the base action in the Smartspace card. */
+    @Nullable
+    private final SmartspaceAction mBaseAction;
+
+    /** A timestamp indicating when the card was created. */
+    @NonNull
+    private final long mCreationTimeMillis;
+
+    /**
+     * A timestamp indicating when the card should be removed from view, in case the service
+     * disconnects or restarts.
+     */
+    @NonNull
+    private final long mExpiryTimeMillis;
+
+    /** A score assigned to a target. */
+    @NonNull
+    private final float mScore;
+
+    /** A {@link List<SmartspaceAction>} containing all action chips. */
+    @NonNull
+    private final List<SmartspaceAction> mActionChips;
+
+    /** A {@link List<SmartspaceAction>} containing all icons for the grid. */
+    @NonNull
+    private final List<SmartspaceAction> mIconGrid;
+
+    /**
+     * {@link FeatureType} indicating the feature type of this card.
+     *
+     * @see FeatureType
+     */
+    @FeatureType
+    @NonNull
+    private final int mFeatureType;
+
+    /**
+     * Indicates whether the content is sensitive. Certain UI surfaces may choose to skip rendering
+     * real content until the device is unlocked.
+     */
+    @NonNull
+    private final boolean mSensitive;
+
+    /** Indicating if the UI should show this target in its expanded state. */
+    @NonNull
+    private final boolean mShouldShowExpanded;
+
+    /** A Notification key if the target was generated using a notification. */
+    @Nullable
+    private final String mSourceNotificationKey;
+
+    /** {@link ComponentName} for this target. */
+    @NonNull
+    private final ComponentName mComponentName;
+
+    /** {@link UserHandle} for this target. */
+    @NonNull
+    private final UserHandle mUserHandle;
+
+    /** Target Ids of other {@link SmartspaceTarget}s if they are associated with this target. */
+    @Nullable
+    private final String mAssociatedSmartspaceTargetId;
+
+    /** {@link Uri} Slice Uri if this target is a slice. */
+    @Nullable
+    private final Uri mSliceUri;
+
+    /** {@link AppWidgetProviderInfo} if this target is a widget. */
+    @Nullable
+    private final AppWidgetProviderInfo mWidgetId;
+
+    public static final int FEATURE_UNDEFINED = 0;
+    public static final int FEATURE_WEATHER = 1;
+    public static final int FEATURE_CALENDAR = 2;
+    public static final int FEATURE_COMMUTE_TIME = 3;
+    public static final int FEATURE_FLIGHT = 4;
+    public static final int FEATURE_TIPS = 5;
+    public static final int FEATURE_REMINDER = 6;
+    public static final int FEATURE_ALARM = 7;
+    public static final int FEATURE_ONBOARDING = 8;
+    public static final int FEATURE_SPORTS = 9;
+    public static final int FEATURE_WEATHER_ALERT = 10;
+    public static final int FEATURE_CONSENT = 11;
+    public static final int FEATURE_STOCK_PRICE_CHANGE = 12;
+    public static final int FEATURE_SHOPPING_LIST = 13;
+    public static final int FEATURE_LOYALTY_CARD = 14;
+    public static final int FEATURE_MEDIA = 15;
+    public static final int FEATURE_BEDTIME_ROUTINE = 16;
+    public static final int FEATURE_FITNESS_TRACKING = 17;
+    public static final int FEATURE_ETA_MONITORING = 18;
+    public static final int FEATURE_MISSED_CALL = 19;
+    public static final int FEATURE_PACKAGE_TRACKING = 20;
+    public static final int FEATURE_TIMER = 21;
+    public static final int FEATURE_STOPWATCH = 22;
+    public static final int FEATURE_UPCOMING_ALARM = 23;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"FEATURE_"}, value = {
+            FEATURE_UNDEFINED,
+            FEATURE_WEATHER,
+            FEATURE_CALENDAR,
+            FEATURE_COMMUTE_TIME,
+            FEATURE_FLIGHT,
+            FEATURE_TIPS,
+            FEATURE_REMINDER,
+            FEATURE_ALARM,
+            FEATURE_ONBOARDING,
+            FEATURE_SPORTS,
+            FEATURE_WEATHER_ALERT,
+            FEATURE_CONSENT,
+            FEATURE_STOCK_PRICE_CHANGE,
+            FEATURE_SHOPPING_LIST,
+            FEATURE_LOYALTY_CARD,
+            FEATURE_MEDIA,
+            FEATURE_BEDTIME_ROUTINE,
+            FEATURE_FITNESS_TRACKING,
+            FEATURE_ETA_MONITORING,
+            FEATURE_MISSED_CALL,
+            FEATURE_PACKAGE_TRACKING,
+            FEATURE_TIMER,
+            FEATURE_STOPWATCH,
+            FEATURE_UPCOMING_ALARM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FeatureType {
+    }
+
+    private SmartspaceTarget(Parcel in) {
+        this.mSmartspaceTargetId = in.readString();
+        this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
+        this.mBaseAction = in.readTypedObject(SmartspaceAction.CREATOR);
+        this.mCreationTimeMillis = in.readLong();
+        this.mExpiryTimeMillis = in.readLong();
+        this.mScore = in.readFloat();
+        this.mActionChips = in.createTypedArrayList(SmartspaceAction.CREATOR);
+        this.mIconGrid = in.createTypedArrayList(SmartspaceAction.CREATOR);
+        this.mFeatureType = in.readInt();
+        this.mSensitive = in.readBoolean();
+        this.mShouldShowExpanded = in.readBoolean();
+        this.mSourceNotificationKey = in.readString();
+        this.mComponentName = in.readTypedObject(ComponentName.CREATOR);
+        this.mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+        this.mAssociatedSmartspaceTargetId = in.readString();
+        this.mSliceUri = in.readTypedObject(Uri.CREATOR);
+        this.mWidgetId = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+    }
+
+    private SmartspaceTarget(String smartspaceTargetId,
+            SmartspaceAction headerAction, SmartspaceAction baseAction, long creationTimeMillis,
+            long expiryTimeMillis, float score,
+            List<SmartspaceAction> actionChips,
+            List<SmartspaceAction> iconGrid, int featureType, boolean sensitive,
+            boolean shouldShowExpanded, String sourceNotificationKey,
+            ComponentName componentName, UserHandle userHandle,
+            String associatedSmartspaceTargetId, Uri sliceUri,
+            AppWidgetProviderInfo widgetId) {
+        mSmartspaceTargetId = smartspaceTargetId;
+        mHeaderAction = headerAction;
+        mBaseAction = baseAction;
+        mCreationTimeMillis = creationTimeMillis;
+        mExpiryTimeMillis = expiryTimeMillis;
+        mScore = score;
+        mActionChips = actionChips;
+        mIconGrid = iconGrid;
+        mFeatureType = featureType;
+        mSensitive = sensitive;
+        mShouldShowExpanded = shouldShowExpanded;
+        mSourceNotificationKey = sourceNotificationKey;
+        mComponentName = componentName;
+        mUserHandle = userHandle;
+        mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+        mSliceUri = sliceUri;
+        mWidgetId = widgetId;
+    }
+
+    /**
+     * Returns the Id of the target.
+     */
+    @NonNull
+    public String getSmartspaceTargetId() {
+        return mSmartspaceTargetId;
+    }
+
+    /**
+     * Returns the header action of the target.
+     */
+    @Nullable
+    public SmartspaceAction getHeaderAction() {
+        return mHeaderAction;
+    }
+
+    /**
+     * Returns the base action of the target.
+     */
+    @Nullable
+    public SmartspaceAction getBaseAction() {
+        return mBaseAction;
+    }
+
+    /**
+     * Returns the creation time of the target.
+     */
+    @NonNull
+    public long getCreationTimeMillis() {
+        return mCreationTimeMillis;
+    }
+
+    /**
+     * Returns the expiry time of the target.
+     */
+    @NonNull
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMillis;
+    }
+
+    /**
+     * Returns the score of the target.
+     */
+    @NonNull
+    public float getScore() {
+        return mScore;
+    }
+
+    /**
+     * Return the action chips of the target.
+     */
+    @NonNull
+    public List<SmartspaceAction> getActionChips() {
+        return mActionChips;
+    }
+
+    /**
+     * Return the icons of the target.
+     */
+    @NonNull
+    public List<SmartspaceAction> getIconGrid() {
+        return mIconGrid;
+    }
+
+    /**
+     * Returns the feature type of the target.
+     */
+    @NonNull
+    public int getFeatureType() {
+        return mFeatureType;
+    }
+
+    /**
+     * Returns whether the target is sensitive or not.
+     */
+    @NonNull
+    public boolean isSensitive() {
+        return mSensitive;
+    }
+
+    /**
+     * Returns whether the target should be shown in expanded state.
+     */
+    @NonNull
+    public boolean shouldShowExpanded() {
+        return mShouldShowExpanded;
+    }
+
+    /**
+     * Returns the source notification key of the target.
+     */
+    @Nullable
+    public String getSourceNotificationKey() {
+        return mSourceNotificationKey;
+    }
+
+    /**
+     * Returns the component name of the target.
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Returns the user handle of the target.
+     */
+    @NonNull
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
+     * Returns the id of a target associated with this instance.
+     */
+    @Nullable
+    public String getAssociatedSmartspaceTargetId() {
+        return mAssociatedSmartspaceTargetId;
+    }
+
+    /**
+     * Returns the slice uri, if the target is a slice.
+     */
+    @Nullable
+    public Uri getSliceUri() {
+        return mSliceUri;
+    }
+
+    /**
+     * Returns the AppWidgetProviderInfo, if the target is a widget.
+     */
+    @Nullable
+    public AppWidgetProviderInfo getWidgetId() {
+        return mWidgetId;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceTarget> CREATOR = new Creator<SmartspaceTarget>() {
+        @Override
+        public SmartspaceTarget createFromParcel(Parcel source) {
+            return new SmartspaceTarget(source);
+        }
+
+        @Override
+        public SmartspaceTarget[] newArray(int size) {
+            return new SmartspaceTarget[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(this.mSmartspaceTargetId);
+        dest.writeTypedObject(this.mHeaderAction, flags);
+        dest.writeTypedObject(this.mBaseAction, flags);
+        dest.writeLong(this.mCreationTimeMillis);
+        dest.writeLong(this.mExpiryTimeMillis);
+        dest.writeFloat(this.mScore);
+        dest.writeTypedList(this.mActionChips);
+        dest.writeTypedList(this.mIconGrid);
+        dest.writeInt(this.mFeatureType);
+        dest.writeBoolean(this.mSensitive);
+        dest.writeBoolean(this.mShouldShowExpanded);
+        dest.writeString(this.mSourceNotificationKey);
+        dest.writeTypedObject(this.mComponentName, flags);
+        dest.writeTypedObject(this.mUserHandle, flags);
+        dest.writeString(this.mAssociatedSmartspaceTargetId);
+        dest.writeTypedObject(this.mSliceUri, flags);
+        dest.writeTypedObject(this.mWidgetId, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceTarget{"
+                + "mSmartspaceTargetId='" + mSmartspaceTargetId + '\''
+                + ", mHeaderAction=" + mHeaderAction
+                + ", mBaseAction=" + mBaseAction
+                + ", mCreationTimeMillis=" + mCreationTimeMillis
+                + ", mExpiryTimeMillis=" + mExpiryTimeMillis
+                + ", mScore=" + mScore
+                + ", mActionChips=" + mActionChips
+                + ", mIconGrid=" + mIconGrid
+                + ", mFeatureType=" + mFeatureType
+                + ", mSensitive=" + mSensitive
+                + ", mShouldShowExpanded=" + mShouldShowExpanded
+                + ", mSourceNotificationKey='" + mSourceNotificationKey + '\''
+                + ", mComponentName=" + mComponentName
+                + ", mUserHandle=" + mUserHandle
+                + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+                + ", mSliceUri=" + mSliceUri
+                + ", mWidgetId=" + mWidgetId
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SmartspaceTarget that = (SmartspaceTarget) o;
+        return mCreationTimeMillis == that.mCreationTimeMillis
+                && mExpiryTimeMillis == that.mExpiryTimeMillis
+                && Float.compare(that.mScore, mScore) == 0
+                && mFeatureType == that.mFeatureType
+                && mSensitive == that.mSensitive
+                && mShouldShowExpanded == that.mShouldShowExpanded
+                && mSmartspaceTargetId.equals(that.mSmartspaceTargetId)
+                && Objects.equals(mHeaderAction, that.mHeaderAction)
+                && Objects.equals(mBaseAction, that.mBaseAction)
+                && Objects.equals(mActionChips, that.mActionChips)
+                && Objects.equals(mIconGrid, that.mIconGrid)
+                && Objects.equals(mSourceNotificationKey, that.mSourceNotificationKey)
+                && mComponentName.equals(that.mComponentName)
+                && mUserHandle.equals(that.mUserHandle)
+                && Objects.equals(mAssociatedSmartspaceTargetId,
+                that.mAssociatedSmartspaceTargetId)
+                && Objects.equals(mSliceUri, that.mSliceUri)
+                && Objects.equals(mWidgetId, that.mWidgetId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
+                mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
+                mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
+                mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+    }
+
+    /**
+     * A builder for {@link SmartspaceTarget} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private final String mSmartspaceTargetId;
+        private SmartspaceAction mHeaderAction;
+        private SmartspaceAction mBaseAction;
+        private long mCreationTimeMillis;
+        private long mExpiryTimeMillis;
+        private float mScore;
+        private List<SmartspaceAction> mActionChips = new ArrayList<>();
+        private List<SmartspaceAction> mIconGrid = new ArrayList<>();
+        private int mFeatureType;
+        private boolean mSensitive;
+        private boolean mShouldShowExpanded;
+        private String mSourceNotificationKey;
+        private final ComponentName mComponentName;
+        private final UserHandle mUserHandle;
+        private String mAssociatedSmartspaceTargetId;
+        private Uri mSliceUri;
+        private AppWidgetProviderInfo mWidgetId;
+
+        /**
+         * A builder for {@link SmartspaceTarget}.
+         *
+         * @param smartspaceTargetId the id of this target
+         * @param componentName      the componentName of this target
+         * @param userHandle         the userHandle of this target
+         */
+        public Builder(@NonNull String smartspaceTargetId,
+                @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
+            this.mSmartspaceTargetId = smartspaceTargetId;
+            this.mComponentName = componentName;
+            this.mUserHandle = userHandle;
+        }
+
+        /**
+         * Sets the header action.
+         */
+        @NonNull
+        public Builder setHeaderAction(@NonNull SmartspaceAction headerAction) {
+            this.mHeaderAction = headerAction;
+            return this;
+        }
+
+        /**
+         * Sets the base action.
+         */
+        @NonNull
+        public Builder setBaseAction(@NonNull SmartspaceAction baseAction) {
+            this.mBaseAction = baseAction;
+            return this;
+        }
+
+        /**
+         * Sets the creation time.
+         */
+        @NonNull
+        public Builder setCreationTimeMillis(@NonNull long creationTimeMillis) {
+            this.mCreationTimeMillis = creationTimeMillis;
+            return this;
+        }
+
+        /**
+         * Sets the expiration time.
+         */
+        @NonNull
+        public Builder setExpiryTimeMillis(@NonNull long expiryTimeMillis) {
+            this.mExpiryTimeMillis = expiryTimeMillis;
+            return this;
+        }
+
+        /**
+         * Sets the score.
+         */
+        @NonNull
+        public Builder setScore(@NonNull float score) {
+            this.mScore = score;
+            return this;
+        }
+
+        /**
+         * Sets the action chips.
+         */
+        @NonNull
+        public Builder setActionChips(@NonNull List<SmartspaceAction> actionChips) {
+            this.mActionChips = actionChips;
+            return this;
+        }
+
+        /**
+         * Sets the icon grid.
+         */
+        @NonNull
+        public Builder setIconGrid(@NonNull List<SmartspaceAction> iconGrid) {
+            this.mIconGrid = iconGrid;
+            return this;
+        }
+
+        /**
+         * Sets the feature type.
+         */
+        @NonNull
+        public Builder setFeatureType(@NonNull int featureType) {
+            this.mFeatureType = featureType;
+            return this;
+        }
+
+        /**
+         * Sets whether the contents are sensitive.
+         */
+        @NonNull
+        public Builder setSensitive(@NonNull boolean sensitive) {
+            this.mSensitive = sensitive;
+            return this;
+        }
+
+        /**
+         * Sets whether to show the card as expanded.
+         */
+        @NonNull
+        public Builder setShouldShowExpanded(@NonNull boolean shouldShowExpanded) {
+            this.mShouldShowExpanded = shouldShowExpanded;
+            return this;
+        }
+
+        /**
+         * Sets the source notification key.
+         */
+        @NonNull
+        public Builder setSourceNotificationKey(@NonNull String sourceNotificationKey) {
+            this.mSourceNotificationKey = sourceNotificationKey;
+            return this;
+        }
+
+        /**
+         * Sets the associated smartspace target id.
+         */
+        @NonNull
+        public Builder setAssociatedSmartspaceTargetId(
+                @NonNull String associatedSmartspaceTargetId) {
+            this.mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+            return this;
+        }
+
+        /**
+         * Sets the slice uri.
+         *
+         * <p><b>NOTE: </b> If {@link mWidgetId} is also set, {@link mSliceUri} should be ignored.
+         */
+        @NonNull
+        public Builder setSliceUri(@NonNull Uri sliceUri) {
+            this.mSliceUri = sliceUri;
+            return this;
+        }
+
+        /**
+         * Sets the widget id.
+         *
+         * <p><b>NOTE: </b> If {@link mWidgetId} is set, all other @Nullable params should be
+         * ignored.
+         */
+        @NonNull
+        public Builder setWidgetId(@NonNull AppWidgetProviderInfo widgetId) {
+            this.mWidgetId = widgetId;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link SmartspaceTarget}.
+         *
+         * @throws IllegalStateException when non null fields are set as null.
+         */
+        @NonNull
+        public SmartspaceTarget build() {
+            if (mSmartspaceTargetId == null
+                    || mComponentName == null
+                    || mUserHandle == null) {
+                throw new IllegalStateException("Please assign a value to all @NonNull args.");
+            }
+            return new SmartspaceTarget(mSmartspaceTargetId,
+                    mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
+                    mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
+                    mSourceNotificationKey, mComponentName, mUserHandle,
+                    mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
new file mode 100644
index 0000000..e797a9b
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace;
+
+parcelable SmartspaceTargetEvent;
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
new file mode 100644
index 0000000..1e0653d
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -0,0 +1,204 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of a smartspace event.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTargetEvent implements Parcelable {
+
+    /**
+     * User interacted with the target.
+     */
+    public static final int EVENT_TARGET_INTERACTION = 1;
+
+    /**
+     * Smartspace target was brought into view.
+     */
+    public static final int EVENT_TARGET_IN_VIEW = 2;
+    /**
+     * Smartspace target went out of view.
+     */
+    public static final int EVENT_TARGET_OUT_OF_VIEW = 3;
+    /**
+     * A dismiss action was issued by the user.
+     */
+    public static final int EVENT_TARGET_DISMISS = 4;
+    /**
+     * A block action was issued by the user.
+     */
+    public static final int EVENT_TARGET_BLOCK = 5;
+    /**
+     * The Ui surface came into view.
+     */
+    public static final int EVENT_UI_SURFACE_IN_VIEW = 6;
+    /**
+     * The Ui surface went out of view.
+     */
+    public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7;
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceTargetEvent> CREATOR =
+            new Creator<SmartspaceTargetEvent>() {
+                public SmartspaceTargetEvent createFromParcel(Parcel parcel) {
+                    return new SmartspaceTargetEvent(parcel);
+                }
+
+                public SmartspaceTargetEvent[] newArray(int size) {
+                    return new SmartspaceTargetEvent[size];
+                }
+            };
+
+    @Nullable
+    private final SmartspaceTarget mSmartspaceTarget;
+
+    @Nullable
+    private final String mSmartspaceActionId;
+
+    @EventType
+    private final int mEventType;
+
+    private SmartspaceTargetEvent(@Nullable SmartspaceTarget smartspaceTarget,
+            @Nullable String smartspaceActionId,
+            @EventType int eventType) {
+        mSmartspaceTarget = smartspaceTarget;
+        mSmartspaceActionId = smartspaceActionId;
+        mEventType = eventType;
+    }
+
+    private SmartspaceTargetEvent(Parcel parcel) {
+        mSmartspaceTarget = parcel.readParcelable(null);
+        mSmartspaceActionId = parcel.readString();
+        mEventType = parcel.readInt();
+    }
+
+    /**
+     * Get the {@link SmartspaceTarget} associated with this event.
+     */
+    @Nullable
+    public SmartspaceTarget getSmartspaceTarget() {
+        return mSmartspaceTarget;
+    }
+
+    /**
+     * Get the action id of the Smartspace Action associated with this event.
+     */
+    @Nullable
+    public String getSmartspaceActionId() {
+        return mSmartspaceActionId;
+    }
+
+    /**
+     * Get the {@link EventType} of this event.
+     */
+    @NonNull
+    @EventType
+    public int getEventType() {
+        return mEventType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mSmartspaceTarget, flags);
+        dest.writeString(mSmartspaceActionId);
+        dest.writeInt(mEventType);
+    }
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"EVENT_"}, value = {
+            EVENT_TARGET_INTERACTION,
+            EVENT_TARGET_IN_VIEW,
+            EVENT_TARGET_OUT_OF_VIEW,
+            EVENT_TARGET_DISMISS,
+            EVENT_TARGET_BLOCK,
+            EVENT_UI_SURFACE_IN_VIEW,
+            EVENT_UI_SURFACE_OUT_OF_VIEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType {
+    }
+
+    /**
+     * A builder for {@link SmartspaceTargetEvent}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @EventType
+        private final int mEventType;
+        @Nullable
+        private SmartspaceTarget mSmartspaceTarget;
+        @Nullable
+        private String mSmartspaceActionId;
+
+        /**
+         * A builder for {@link SmartspaceTargetEvent}.
+         */
+        public Builder(@EventType int eventType) {
+            mEventType = eventType;
+        }
+
+        /**
+         * Sets the SmartspaceTarget for this event.
+         */
+        @NonNull
+        public Builder setSmartspaceTarget(@NonNull SmartspaceTarget smartspaceTarget) {
+            mSmartspaceTarget = smartspaceTarget;
+            return this;
+        }
+
+        /**
+         * Sets the Smartspace action id for this event.
+         */
+        @NonNull
+        public Builder setSmartspaceActionId(@NonNull String smartspaceActionId) {
+            mSmartspaceActionId = smartspaceActionId;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link SmartspaceTargetEvent} instance.
+         */
+        @NonNull
+        public SmartspaceTargetEvent build() {
+            return new SmartspaceTargetEvent(mSmartspaceTarget, mSmartspaceActionId, mEventType);
+        }
+    }
+}
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 93d96d0..e96e22c4 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -406,6 +406,14 @@
         return new UserHandle(UserHandle.getUserId(providerInfo.applicationInfo.uid));
     }
 
+    /**
+     * Returns the broadcast receiver that is providing this widget.
+     */
+    @NonNull
+    public ActivityInfo getProviderInfo() {
+        return providerInfo;
+    }
+
     @Override
     @SuppressWarnings("deprecation")
     public void writeToParcel(Parcel out, int flags) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 125b5ff..4dc41b2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
 import android.annotation.UiContext;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.GameManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
@@ -4625,6 +4626,18 @@
     public static final String SEARCH_UI_SERVICE = "search_ui";
 
     /**
+     * Used for getting the smartspace service.
+     *
+     * <p><b>NOTE: </b> this service is optional; callers of
+     * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String SMARTSPACE_SERVICE = "smartspace";
+
+    /**
      * Use with {@link #getSystemService(String)} to access the
      * {@link com.android.server.voiceinteraction.SoundTriggerService}.
      *
@@ -5429,7 +5442,7 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.graphics.GameManager}.
+     * {@link GameManager}.
      *
      * @see #getSystemService(String)
      *
@@ -6154,6 +6167,7 @@
     @UiContext
     @NonNull
     public Context createWindowContext(@NonNull Display display, @WindowType int type,
+            @SuppressLint("NullableCollection")
             @Nullable Bundle options) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 82d7b63..16e720e 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,8 +33,6 @@
  * and badged icon for the activity.
  */
 public class LauncherActivityInfo {
-    private static final String TAG = "LauncherActivityInfo";
-
     private final PackageManager mPm;
     private UserHandle mUser;
     private final LauncherActivityInfoInternal mInternal;
@@ -81,7 +80,7 @@
      */
     public CharSequence getLabel() {
         // TODO: Go through LauncherAppsService
-        return mInternal.getActivityInfo().loadLabel(mPm);
+        return getActivityInfo().loadLabel(mPm);
     }
 
     /**
@@ -101,20 +100,20 @@
      */
     public Drawable getIcon(int density) {
         // TODO: Go through LauncherAppsService
-        final int iconRes = mInternal.getActivityInfo().getIconResource();
+        final int iconRes = getActivityInfo().getIconResource();
         Drawable icon = null;
         // Get the preferred density icon from the app's resources
         if (density != 0 && iconRes != 0) {
             try {
                 final Resources resources = mPm.getResourcesForApplication(
-                        mInternal.getActivityInfo().applicationInfo);
+                        getActivityInfo().applicationInfo);
                 icon = resources.getDrawableForDensity(iconRes, density);
             } catch (NameNotFoundException | Resources.NotFoundException exc) {
             }
         }
         // Get the default density icon
         if (icon == null) {
-            icon = mInternal.getActivityInfo().loadIcon(mPm);
+            icon = getActivityInfo().loadIcon(mPm);
         }
         return icon;
     }
@@ -126,25 +125,25 @@
      * @hide remove before shipping
      */
     public int getApplicationFlags() {
-        return mInternal.getActivityInfo().flags;
+        return getActivityInfo().flags;
     }
 
     /**
      * Returns the ActivityInfo of the activity.
      *
      * @return Activity Info
-     * @hide
      */
+    @NonNull
     public ActivityInfo getActivityInfo() {
         return mInternal.getActivityInfo();
     }
 
     /**
-     * Returns the application info for the appliction this activity belongs to.
+     * Returns the application info for the application this activity belongs to.
      * @return
      */
     public ApplicationInfo getApplicationInfo() {
-        return mInternal.getActivityInfo().applicationInfo;
+        return getActivityInfo().applicationInfo;
     }
 
     /**
@@ -155,7 +154,7 @@
     public long getFirstInstallTime() {
         try {
             // TODO: Go through LauncherAppsService
-            return mPm.getPackageInfo(mInternal.getActivityInfo().packageName,
+            return mPm.getPackageInfo(getActivityInfo().packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
         } catch (NameNotFoundException nnfe) {
             // Sorry, can't find package
@@ -164,11 +163,11 @@
     }
 
     /**
-     * Returns the name for the acitivty from  android:name in the manifest.
-     * @return the name from android:name for the acitivity.
+     * Returns the name for the activity from  android:name in the manifest.
+     * @return the name from android:name for the activity.
      */
     public String getName() {
-        return mInternal.getActivityInfo().name;
+        return getActivityInfo().name;
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index f991306..e8ef077 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -222,7 +222,7 @@
      * &lt;attribution&gt;} tags included under &lt;manifest&gt;, or null if there were none. This
      * is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS} was set.
      */
-    @SuppressWarnings("ArrayReturn")
+    @SuppressWarnings({"ArrayReturn", "NullableCollection"})
     public @Nullable Attribution[] attributions;
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9ae9c25..72fb1ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -54,6 +54,7 @@
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
@@ -124,6 +125,19 @@
     }
 
     /**
+     * &lt;application&gt; level {@link android.content.pm.PackageManager.Property} tag specifying
+     * the XML resource ID containing an application's media capabilities XML file
+     *
+     * For example:
+     * &lt;application&gt;
+     *   &lt;property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+     *     android:resource="@xml/media_capabilities"&gt;
+     * &lt;application&gt;
+     */
+    public static final String PROPERTY_MEDIA_CAPABILITIES =
+            "android.media.PROPERTY_MEDIA_CAPABILITIES";
+
+    /**
      * A property value set within the manifest.
      * <p>
      * The value of a property will only have a single type, as defined by
@@ -6703,6 +6717,22 @@
             throws NameNotFoundException;
 
     /**
+     * Retrieve the resources for an application for the provided configuration.
+     *
+     * @param app Information about the desired application.
+     * @param configuration Overridden configuration when loading the Resources
+     *
+     * @return Returns the application's Resources.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded (most likely because it was uninstalled).
+     */
+    @NonNull
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
+            Configuration configuration) throws NameNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Retrieve the resources associated with an application.  Given the full
      * package name of an application, retrieves the information about it and
      * calls getResources() to return its application's resources.  If the
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 02fb06b..bce4b87 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -256,6 +256,8 @@
         try {
             if (obj != null) {
                 Signature other = (Signature)obj;
+                // Note, some classes, such as PackageParser.SigningDetails, rely on equals
+                // only comparing the mSignature arrays without the flags.
                 return this == other || Arrays.equals(mSignature, other.mSignature);
             }
         } catch (ClassCastException e) {
@@ -268,6 +270,8 @@
         if (mHaveHashCode) {
             return mHashCode;
         }
+        // Note, similar to equals some classes rely on the hash code not including
+        // the flags for Set membership checks.
         mHashCode = Arrays.hashCode(mSignature);
         mHaveHashCode = true;
         return mHashCode;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index f6edb2e..abf694f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -89,6 +89,11 @@
     private static final int NEEDS_COMPAT_RES = 16;
 
     /**
+     * Set if the application needs to be forcibly downscaled
+     */
+    private static final int HAS_OVERRIDE_SCALING = 32;
+
+    /**
      * The effective screen density we have selected for this application.
      */
     public final int applicationDensity;
@@ -107,6 +112,11 @@
     @UnsupportedAppUsage
     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
             boolean forceCompat) {
+        this(appInfo, screenLayout, sw, forceCompat, 1f);
+    }
+
+    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+            boolean forceCompat, float overrideScale) {
         int compatFlags = 0;
 
         if (appInfo.targetSdkVersion < VERSION_CODES.O) {
@@ -241,7 +251,12 @@
                 compatFlags |= NEVER_NEEDS_COMPAT;
             }
 
-            if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+            if (overrideScale != 1.0f) {
+                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
+                applicationScale = overrideScale;
+                applicationInvertedScale = 1.0f / overrideScale;
+                compatFlags |= HAS_OVERRIDE_SCALING;
+            } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
                 applicationScale = 1.0f;
                 applicationInvertedScale = 1.0f;
@@ -277,7 +292,7 @@
      */
     @UnsupportedAppUsage
     public boolean isScalingRequired() {
-        return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
+        return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
     }
     
     @UnsupportedAppUsage
@@ -303,7 +318,7 @@
      */
     @UnsupportedAppUsage
     public Translator getTranslator() {
-        return isScalingRequired() ? new Translator() : null;
+        return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
     }
 
     /**
@@ -504,6 +519,16 @@
         if (isScalingRequired()) {
             float invertedRatio = applicationInvertedScale;
             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+            inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
+            inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
+            inoutConfig.smallestScreenWidthDp =
+                    (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
+            inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
+            inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
+            final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
+            if (appBounds != null) {
+                appBounds.scale(invertedRatio);
+            }
         }
     }
 
diff --git a/core/java/android/hardware/Battery.java b/core/java/android/hardware/Battery.java
new file mode 100644
index 0000000..24c8d76
--- /dev/null
+++ b/core/java/android/hardware/Battery.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.os.BatteryManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Battery class is a representation of a single battery on a device.
+ */
+public abstract class Battery {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_CHARGING,
+            STATUS_DISCHARGING,
+            STATUS_NOT_CHARGING,
+            STATUS_FULL
+    })
+    public @interface BatteryStatus {
+    }
+
+    /** Battery status is unknown. */
+    public static final int STATUS_UNKNOWN = BatteryManager.BATTERY_STATUS_UNKNOWN;
+    /** Battery is charging. */
+    public static final int STATUS_CHARGING = BatteryManager.BATTERY_STATUS_CHARGING;
+    /** Battery is discharging. */
+    public static final int STATUS_DISCHARGING = BatteryManager.BATTERY_STATUS_DISCHARGING;
+    /** Battery is connected to power but not charging. */
+    public static final int STATUS_NOT_CHARGING = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+    /** Battery is full. */
+    public static final int STATUS_FULL = BatteryManager.BATTERY_STATUS_FULL;
+
+    /**
+     * Check whether the hardware has a battery.
+     *
+     * @return True if the hardware has a battery, else false.
+     */
+    public abstract boolean hasBattery();
+
+    /**
+     * Get the battery status.
+     *
+     * @return the battery status.
+     */
+    public abstract @BatteryStatus int getStatus();
+
+    /**
+     * Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity
+     * Returns -1 when battery capacity can't be read.
+     *
+     * @return the battery capacity.
+     */
+    public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity();
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b39df4d..c69c47f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -93,6 +93,10 @@
     int[] getVibratorIds(int deviceId);
     boolean isVibrating(int deviceId);
 
+    // Input device battery query.
+    int getBatteryStatus(int deviceId);
+    int getBatteryCapacity(int deviceId);
+
     void setPointerIconType(int typeId);
     void setCustomPointerIcon(in PointerIcon icon);
 
diff --git a/core/java/android/hardware/input/InputDeviceBattery.java b/core/java/android/hardware/input/InputDeviceBattery.java
new file mode 100644
index 0000000..0fe124e
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceBattery.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
+
+import android.hardware.Battery;
+
+/**
+ * Battery implementation for input devices.
+ *
+ * @hide
+ */
+public final class InputDeviceBattery extends Battery {
+    private static final float NULL_BATTERY_CAPACITY = -1.0f;
+
+    private final InputManager mInputManager;
+    private final int mDeviceId;
+    private final boolean mHasBattery;
+
+    InputDeviceBattery(InputManager inputManager, int deviceId, boolean hasBattery) {
+        mInputManager = inputManager;
+        mDeviceId = deviceId;
+        mHasBattery = hasBattery;
+    }
+
+    @Override
+    public boolean hasBattery() {
+        return mHasBattery;
+    }
+
+    @Override
+    public int getStatus() {
+        if (!mHasBattery) {
+            return BATTERY_STATUS_UNKNOWN;
+        }
+        return mInputManager.getBatteryStatus(mDeviceId);
+    }
+
+    @Override
+    public float getCapacity() {
+        if (mHasBattery) {
+            int capacity = mInputManager.getBatteryCapacity(mDeviceId);
+            if (capacity != INVALID_BATTERY_CAPACITY) {
+                return (float) capacity / 100.0f;
+            }
+        }
+        return NULL_BATTERY_CAPACITY;
+    }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e63dc11..185c59d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1245,6 +1245,32 @@
     }
 
     /**
+     * Get the battery status of the input device
+     * @param deviceId The input device ID
+     * @hide
+     */
+    public int getBatteryStatus(int deviceId) {
+        try {
+            return mIm.getBatteryStatus(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the remaining battery capacity of the input device
+     * @param deviceId The input device ID
+     * @hide
+     */
+    public int getBatteryCapacity(int deviceId) {
+        try {
+            return mIm.getBatteryCapacity(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
      * @param inputPort The port of the input device.
@@ -1471,6 +1497,15 @@
     }
 
     /**
+     * Gets a battery object associated with an input device, assuming it has one.
+     * @return The battery, never null.
+     * @hide
+     */
+    public InputDeviceBattery getInputDeviceBattery(int deviceId, boolean hasBattery) {
+        return new InputDeviceBattery(this, deviceId, hasBattery);
+    }
+
+    /**
      * Listens for changes in input devices.
      */
     public interface InputDeviceListener {
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index 20fa753..b31b85f 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 import java.util.concurrent.Executor;
@@ -101,4 +102,34 @@
      * @param nanoAppId the ID of the nanoapp that had been disabled
      */
     public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {}
+
+    /**
+     * Callback invoked when a {@link ContextHubClient}'s authorization to communicate with a
+     * nanoapp changes. This typically happens as a result of the app that created the
+     * {@link ContextHubClient} gaining or losing the permissions required to communicate with a
+     * nanoapp.
+     *
+     * An example of the connection callbacks looks like:
+     * 1) {@link ContextHubClient} sends message to nanoapp and holds required permissions
+     * 2) {@link ContextHubClient} loses required permissions
+     * 3) Callback invoked with the nanoapp ID and
+     *    {@link ContextHubManager#AUTHORIZATION_DENIED_GRACE_PERIOD}
+     * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
+     * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
+     *    At this point, any further attempts of communication between the nanoapp and the
+     *    {@link ContextHubClient} will be dropped by the contexthub along with
+     *    {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
+     *    should assume no communciation can happen again until
+     *    {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+     *
+     * @param client the client that is associated with this callback
+     * @param nanoAppId the ID of the nanoapp associated with the new
+     * authorization state
+     * @param authorization the authorization state denoting the ability of the
+     * client to communicate with the nanoapp
+     */
+    public void onClientAuthorizationChanged(
+            @NonNull ContextHubClient client,
+            long nanoAppId,
+            @ContextHubManager.AuthorizationState int authorization) {}
 }
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 917f62b..3e8f421 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -43,39 +43,45 @@
 
     private final int mNanoAppAbortCode;
 
+    private final int mClientAuthorizationState;
+
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
-            long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode) {
+            long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode,
+            @ContextHubManager.AuthorizationState int clientAuthorizationState) {
         mContextHubInfo = contextHubInfo;
         mEventType = eventType;
         mNanoAppId = nanoAppId;
         mNanoAppMessage = nanoAppMessage;
         mNanoAppAbortCode = nanoAppAbortCode;
+        mClientAuthorizationState = clientAuthorizationState;
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType) {
         this(contextHubInfo, eventType, -1 /* nanoAppId */, null /* nanoAppMessage */,
-                -1 /* nanoAppAbortCode */);
+                -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId) {
         this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */,
-                -1 /* nanoAppAbortCode */);
+                -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId, @NonNull NanoAppMessage nanoAppMessage) {
-        this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */);
+        this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */,
+                0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId, int nanoAppAbortCode) {
-        this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode);
+        this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode,
+                0 /* clientAuthorizationState */);
     }
 
     /**
@@ -105,7 +111,8 @@
             case ContextHubManager.EVENT_NANOAPP_ENABLED:
             case ContextHubManager.EVENT_NANOAPP_DISABLED:
             case ContextHubManager.EVENT_NANOAPP_ABORTED:
-            case ContextHubManager.EVENT_NANOAPP_MESSAGE: // fall through
+            case ContextHubManager.EVENT_NANOAPP_MESSAGE:
+            case ContextHubManager.EVENT_CLIENT_AUTHORIZATION: // fall through
                 long nanoAppId = getLongExtraOrThrow(intent, ContextHubManager.EXTRA_NANOAPP_ID);
                 if (eventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
                     hasExtraOrThrow(intent, ContextHubManager.EXTRA_MESSAGE);
@@ -120,6 +127,11 @@
                     int nanoAppAbortCode = getIntExtraOrThrow(
                             intent, ContextHubManager.EXTRA_NANOAPP_ABORT_CODE);
                     event = new ContextHubIntentEvent(info, eventType, nanoAppId, nanoAppAbortCode);
+                } else if (eventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+                    int authState = getIntExtraOrThrow(
+                            intent, ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE);
+                    event = new ContextHubIntentEvent(info, eventType, nanoAppId,
+                            null /* nanoAppMessage */, -1 /* nanoAppAbortCode */, authState);
                 } else {
                     event = new ContextHubIntentEvent(info, eventType, nanoAppId);
                 }
@@ -192,6 +204,21 @@
         return mNanoAppMessage;
     }
 
+    /**
+     * @return the client authorization state
+     *
+     * @throws UnsupportedOperationException if this was not a client authorization state event
+     */
+    @ContextHubManager.AuthorizationState
+    public int getClientAuthorizationState() {
+        if (mEventType != ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+            throw new UnsupportedOperationException(
+                    "Cannot invoke getClientAuthorizationState() on non-authorization event: "
+                    + mEventType);
+        }
+        return mClientAuthorizationState;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -207,6 +234,9 @@
         if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
             out += ", nanoAppMessage = " + mNanoAppMessage;
         }
+        if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+            out += ", clientAuthState = " + mClientAuthorizationState;
+        }
 
         return out + "]";
     }
@@ -233,6 +263,9 @@
                     if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
                         isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
                     }
+                    if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+                        isEqual &= other.getClientAuthorizationState() == mClientAuthorizationState;
+                    }
                 } catch (UnsupportedOperationException e) {
                     isEqual = false;
                 }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d444807..ebb3021 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -59,6 +59,12 @@
     private static final String TAG = "ContextHubManager";
 
     /**
+     * An extra of type int describing the client's authorization state.
+     */
+    public static final String EXTRA_CLIENT_AUTHORIZATION_STATE =
+            "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
+
+    /**
      * An extra of type {@link ContextHubInfo} describing the source of the event.
      */
     public static final String EXTRA_CONTEXT_HUB_INFO =
@@ -86,6 +92,42 @@
     public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
 
     /**
+     * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to
+     * communicate.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "AUTHORIZATION_" }, value = {
+        AUTHORIZATION_DENIED,
+        AUTHORIZATION_DENIED_GRACE_PERIOD,
+        AUTHORIZATION_GRANTED,
+    })
+    public @interface AuthorizationState { }
+
+    /**
+     * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the
+     * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to
+     * receive this authorization state if the connection is still closed.
+     */
+    public static final int AUTHORIZATION_DENIED = 0;
+
+    /**
+     * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a
+     * nanoapp. The {@link ContextHubClient} must perform any cleanup with the nanoapp as soon as
+     * possible.
+     *
+     * Note that the time between this state event and {@link AUTHORIZATION_DENIED} must be enough
+     * for the {@link ContextHubClient} to send at least one message to the nanoapp.
+     */
+    public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1;
+
+    /**
+     * The {@link ContextHubClient} is authorized to communicate with the nanoapp.
+     */
+    public static final int AUTHORIZATION_GRANTED = 2;
+
+    /**
      * Constants describing the type of events from a Context Hub.
      * {@hide}
      */
@@ -98,6 +140,7 @@
         EVENT_NANOAPP_ABORTED,
         EVENT_NANOAPP_MESSAGE,
         EVENT_HUB_RESET,
+        EVENT_CLIENT_AUTHORIZATION,
     })
     public @interface Event { }
 
@@ -138,6 +181,14 @@
      */
     public static final int EVENT_HUB_RESET = 6;
 
+    /**
+     * An event describing a client authorization state change. See
+     * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this
+     * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE
+     * extras.
+     */
+    public static final int EVENT_CLIENT_AUTHORIZATION = 7;
+
     private final Looper mMainLooper;
     private final IContextHubService mService;
     private Callback mCallback;
@@ -747,6 +798,14 @@
             public void onNanoAppDisabled(long nanoAppId) {
                 executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
             }
+
+            @Override
+            public void onClientAuthorizationChanged(
+                    long nanoAppId, @ContextHubManager.AuthorizationState int authorization) {
+                executor.execute(
+                        () -> callback.onClientAuthorizationChanged(
+                                client, nanoAppId, authorization));
+            }
         };
     }
 
@@ -757,9 +816,10 @@
      * registration succeeds, the client can send messages to nanoapps through the returned
      * {@link ContextHubClient} object, and receive notifications through the provided callback.
      *
+     * @param context  the context of the application
      * @param hubInfo  the hub to attach this client to
-     * @param callback the notification callback to register
      * @param executor the executor to invoke the callback
+     * @param callback the notification callback to register
      * @return the registered client object
      *
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
@@ -773,8 +833,9 @@
             android.Manifest.permission.ACCESS_CONTEXT_HUB
     })
     @NonNull public ContextHubClient createClient(
-            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
+            @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ContextHubClientCallback callback) {
         Objects.requireNonNull(callback, "Callback cannot be null");
         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
         Objects.requireNonNull(executor, "Executor cannot be null");
@@ -783,9 +844,14 @@
         IContextHubClientCallback clientInterface = createClientCallback(
                 client, callback, executor);
 
+        String attributionTag = null;
+        if (context != null) {
+            attributionTag = context.getAttributionTag();
+        }
+
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.createClient(hubInfo.getId(), clientInterface);
+            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -794,19 +860,25 @@
         return client;
     }
 
+
+    /**
+     * Equivalent to
+     * {@link #createClient(ContextHubInfo, Executor, String, ContextHubClientCallback)}
+     * with the {@link Context} being set to null.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.LOCATION_HARDWARE,
+            android.Manifest.permission.ACCESS_CONTEXT_HUB
+    })
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
+        return createClient(null /* context */, hubInfo, executor, callback);
+    }
+
     /**
      * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
      * with the executor using the main thread's Looper.
-     *
-     * @param hubInfo  the hub to attach this client to
-     * @param callback the notification callback to register
-     * @return the registered client object
-     *
-     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
-     * @throws IllegalStateException    if there were too many registered clients at the service
-     * @throws NullPointerException     if callback or hubInfo is null
-     *
-     * @see ContextHubClientCallback
      */
     @RequiresPermission(anyOf = {
             android.Manifest.permission.LOCATION_HARDWARE,
@@ -814,7 +886,8 @@
     })
     @NonNull public ContextHubClient createClient(
             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
-        return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
+        return createClient(null /* context */, hubInfo, new HandlerExecutor(Handler.getMain()),
+                            callback);
     }
 
     /**
@@ -848,6 +921,8 @@
      * on the provided PendingIntent, then the client will be automatically unregistered by the
      * service.
      *
+     * @param context       the context of the application. If a PendingIntent client is recreated,
+     * the latest state in the context will be used and old state will be discarded
      * @param hubInfo       the hub to attach this client to
      * @param pendingIntent the PendingIntent to register to the client
      * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
@@ -862,16 +937,22 @@
             android.Manifest.permission.ACCESS_CONTEXT_HUB
     })
     @NonNull public ContextHubClient createClient(
-            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+            @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+            @NonNull PendingIntent pendingIntent, long nanoAppId) {
         Objects.requireNonNull(pendingIntent);
         Objects.requireNonNull(hubInfo);
 
         ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
 
+        String attributionTag = null;
+        if (context != null) {
+            attributionTag = context.getAttributionTag();
+        }
+
         IContextHubClient clientProxy;
         try {
             clientProxy = mService.createPendingIntentClient(
-                    hubInfo.getId(), pendingIntent, nanoAppId);
+                    hubInfo.getId(), pendingIntent, nanoAppId, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -881,6 +962,19 @@
     }
 
     /**
+     * Equivalent to {@link #createClient(ContextHubInfo, PendingIntent, long, String)}
+     * with {@link Context} being set to null.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.LOCATION_HARDWARE,
+            android.Manifest.permission.ACCESS_CONTEXT_HUB
+    })
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+        return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId);
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl
index 1c76bcb..bcd6b08 100644
--- a/core/java/android/hardware/location/IContextHubClientCallback.aidl
+++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl
@@ -46,4 +46,7 @@
 
     // Callback invoked when a nanoapp is disabled at the attached Context Hub.
     void onNanoAppDisabled(long nanoAppId);
+
+    // Callback invoked when the authorization state of a client changes.
+    void onClientAuthorizationChanged(long nanoAppId, int authorization);
 }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 04cc563..4961195 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -59,11 +59,13 @@
     int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
 
     // Creates a client to send and receive messages
-    IContextHubClient createClient(int contextHubId, in IContextHubClientCallback client);
+    IContextHubClient createClient(
+            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
 
     // Creates a PendingIntent-based client to send and receive messages
     IContextHubClient createPendingIntentClient(
-            int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
+            int contextHubId, in PendingIntent pendingIntent, long nanoAppId,
+            in String attributionTag);
 
     // Returns a list of ContextHub objects of available hubs
     List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 8de7ecd..96b1f19 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -15,10 +15,14 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A class describing the nanoapp state information resulting from a query to a Context Hub.
  *
@@ -29,11 +33,21 @@
     private long mNanoAppId;
     private int mNanoAppVersion;
     private boolean mIsEnabled;
+    private List<String> mNanoAppPermissions;
 
     public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
         mNanoAppId = nanoAppId;
         mNanoAppVersion = appVersion;
         mIsEnabled = enabled;
+        mNanoAppPermissions = new ArrayList<String>();
+    }
+
+    public NanoAppState(long nanoAppId, int appVersion, boolean enabled,
+                        @NonNull List<String> nanoAppPermissions) {
+        mNanoAppId = nanoAppId;
+        mNanoAppVersion = appVersion;
+        mIsEnabled = enabled;
+        mNanoAppPermissions = nanoAppPermissions;
     }
 
     /**
@@ -57,10 +71,19 @@
         return mIsEnabled;
     }
 
+    /**
+     * @return List of Android permissions that are required to communicate with this app.
+     */
+    public @NonNull List<String> getNanoAppPermissions() {
+        return mNanoAppPermissions;
+    }
+
     private NanoAppState(Parcel in) {
         mNanoAppId = in.readLong();
         mNanoAppVersion = in.readInt();
         mIsEnabled = (in.readInt() == 1);
+        mNanoAppPermissions = new ArrayList<String>();
+        in.readStringList(mNanoAppPermissions);
     }
 
     @Override
@@ -73,6 +96,7 @@
         out.writeLong(mNanoAppId);
         out.writeInt(mNanoAppVersion);
         out.writeInt(mIsEnabled ? 1 : 0);
+        out.writeStringList(mNanoAppPermissions);
     }
 
     public static final @android.annotation.NonNull Creator<NanoAppState> CREATOR =
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 250cff2..a22d41a 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -26,7 +26,7 @@
 import android.widget.TextView;
 
 import com.android.internal.util.HexDump;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
+import com.android.internal.org.bouncycastle.asn1.x509.X509Name;
 
 import java.io.ByteArrayInputStream;
 import java.math.BigInteger;
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 33beb6a..fa090f5 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -67,7 +67,6 @@
 public class VcnManager {
     @NonNull private static final String TAG = VcnManager.class.getSimpleName();
 
-    /** @hide */
     @VisibleForTesting
     public static final Map<
                     VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index 869a727..cb4e9cb 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -364,8 +364,22 @@
         @Override
         public long getDuration() {
             long maxDuration = Long.MIN_VALUE;
+            boolean hasUnknownStep = false;
             for (int i = 0; i < mEffects.size(); i++) {
-                maxDuration = Math.max(maxDuration, mEffects.valueAt(i).getDuration());
+                long duration = mEffects.valueAt(i).getDuration();
+                if (duration == Long.MAX_VALUE) {
+                    // If any duration is repeating, this combination duration is also repeating.
+                    return duration;
+                }
+                maxDuration = Math.max(maxDuration, duration);
+                // If any step is unknown, this combination duration will also be unknown, unless
+                // any step is repeating. Repeating vibrations take precedence over non-repeating
+                // ones in the service, so continue looping to check for repeating steps.
+                hasUnknownStep |= duration < 0;
+            }
+            if (hasUnknownStep) {
+                // If any step is unknown, this combination duration is also unknown.
+                return -1;
             }
             return maxDuration;
         }
@@ -477,16 +491,25 @@
 
         @Override
         public long getDuration() {
+            boolean hasUnknownStep = false;
             long durations = 0;
             final int effectCount = mEffects.size();
             for (int i = 0; i < effectCount; i++) {
                 CombinedVibrationEffect effect = mEffects.get(i);
                 long duration = effect.getDuration();
-                if (duration < 0) {
-                    // If any duration is unknown, this combination duration is also unknown.
+                if (duration == Long.MAX_VALUE) {
+                    // If any duration is repeating, this combination duration is also repeating.
                     return duration;
                 }
                 durations += duration;
+                // If any step is unknown, this combination duration will also be unknown, unless
+                // any step is repeating. Repeating vibrations take precedence over non-repeating
+                // ones in the service, so continue looping to check for repeating steps.
+                hasUnknownStep |= duration < 0;
+            }
+            if (hasUnknownStep) {
+                // If any step is unknown, this combination duration is also unknown.
+                return -1;
             }
             long delays = 0;
             for (int i = 0; i < effectCount; i++) {
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 804dc10..f9e2947 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.os.CombinedVibrationEffect;
+import android.os.IVibratorStateListener;
 import android.os.VibrationAttributes;
 import android.os.VibratorInfo;
 
@@ -24,6 +25,9 @@
 interface IVibratorManagerService {
     int[] getVibratorIds();
     VibratorInfo getVibratorInfo(int vibratorId);
+    boolean isVibrating(int vibratorId);
+    boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
+    boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
     boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
             in CombinedVibrationEffect effect, in VibrationAttributes attributes);
     void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 2093077..217f178 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -268,7 +268,7 @@
     }
 
     /** @hide */
-    public String usageToString(int usage) {
+    public static String usageToString(int usage) {
         switch (usage) {
             case USAGE_UNKNOWN:
                 return "UNKNOWN";
diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
index 647db17..11d26ca 100644
--- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java
+++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
@@ -16,19 +16,20 @@
 
 package android.os.strictmode;
 
+import android.annotation.NonNull;
 import android.content.Context;
 
 /**
- * Incorrect usage of {@link Context}, such as obtaining a visual service from non-visual
- * {@link Context} instance.
+ * Incorrect usage of {@link Context}, such as obtaining a UI service from non-UI {@link Context}
+ * instance.
+ *
  * @see Context#getSystemService(String)
- * @see Context#getDisplayNoVerify()
- * @hide
+ * @see Context#isUiContext(Context)
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
  */
 public final class IncorrectContextUseViolation extends Violation {
 
-    /** @hide */
-    public IncorrectContextUseViolation(String message, Throwable originStack) {
+    public IncorrectContextUseViolation(@NonNull String message, @NonNull Throwable originStack) {
         super(message);
         initCause(originStack);
     }
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index 4224b7a..dfe748b 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -809,6 +809,9 @@
 permissions. It is unlikely that 3rd party apps will be able to use APIs protected by signature
 permissions as they are usually not signed with the platform certificate.
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 ### Preinstalled permissions
@@ -819,6 +822,9 @@
 Hence this permission level is discouraged unless there are
 [further restrictions](#restricted-by-tests).
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 ### Privileged permissions
@@ -833,6 +839,9 @@
 Hence this permission level is discouraged unless there are
 [further restrictions](#restricted-by-tests).
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 #### Restricted by tests
@@ -890,8 +899,16 @@
 Which apps qualify for such a permission level is flexible and custom for each such level. Usually
 they refer to a single or small set of apps, usually - but not always - apps defined in AOSP.
 
+This type of permission is deprecated in favor of
+[role protected permissions](#role-protected-permissions).
+
 These permissions are defined and checked like an install time permission.
 
+### Role protected permissions
+
+See
+[Using role for permission protection](../../../../../../packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md).
+
 ### Development permissions
 
 > Not recommended
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6dab4c0..f6ef8a3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13393,6 +13393,24 @@
                 "euicc_factory_reset_timeout_millis";
 
         /**
+         * Flag to set the waiting time for euicc slot switch.
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS =
+                "euicc_switch_slot_timeout_millis";
+
+        /**
+         * Flag to set the waiting time for enabling multi SIM slot.
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS =
+                "enable_multi_slot_timeout_millis";
+
+        /**
          * Flag to set the timeout for when to refresh the storage settings cached data.
          * Type: long
          *
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 4679c56..e3d0741 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -129,14 +129,14 @@
 
     /** @hide */
     @TestApi
-    @SuppressLint("ConcreteCollection")
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
     public @Nullable ArrayList<AutofillId> getFieldIds() {
         return mFieldIds;
     }
 
     /** @hide */
     @TestApi
-    @SuppressLint("ConcreteCollection")
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
     public @Nullable ArrayList<AutofillValue> getFieldValues() {
         return mFieldValues;
     }
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index e35b8b7..ad6316c 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -119,6 +119,7 @@
                     IoUtils.closeQuietly(control.incremental.cmd);
                     IoUtils.closeQuietly(control.incremental.pendingReads);
                     IoUtils.closeQuietly(control.incremental.log);
+                    IoUtils.closeQuietly(control.incremental.blocksWritten);
                 }
             }
         }
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index e0f3018..44daeff 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -46,7 +46,7 @@
     void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType);
 
     // assistants only
-    void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
+    void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
     void onPanelRevealed(int items);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index cf2152c..1d49a72 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -126,7 +126,7 @@
      * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)}.</p>
      *
      * @param sbn the new notification
-     * @return an adjustment or null to take no action, within 100ms.
+     * @return an adjustment or null to take no action, within 200ms.
      */
     abstract public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn);
 
@@ -135,7 +135,7 @@
      *
      * @param sbn the new notification
      * @param channel the channel the notification was posted to
-     * @return an adjustment or null to take no action, within 100ms.
+     * @return an adjustment or null to take no action, within 200ms.
      */
     public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
             @NonNull NotificationChannel channel) {
@@ -143,6 +143,20 @@
     }
 
     /**
+     * A notification was posted by an app. Called before post.
+     *
+     * @param sbn the new notification
+     * @param channel the channel the notification was posted to
+     * @param rankingMap The current ranking map that can be used to retrieve ranking information
+     *                   for active notifications.
+     * @return an adjustment or null to take no action, within 200ms.
+     */
+    public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
+            @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap) {
+        return onNotificationEnqueued(sbn, channel);
+    }
+
+    /**
      * Implement this method to learn when notifications are removed, how they were interacted with
      * before removal, and why they were removed.
      * <p>
@@ -316,7 +330,7 @@
     private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
         @Override
         public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder,
-                NotificationChannel channel) {
+                NotificationChannel channel, NotificationRankingUpdate update) {
             StatusBarNotification sbn;
             try {
                 sbn = sbnHolder.get();
@@ -330,9 +344,11 @@
                 return;
             }
 
+            applyUpdateLocked(update);
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = sbn;
             args.arg2 = channel;
+            args.arg3 = getCurrentRanking();
             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
                     args).sendToTarget();
         }
@@ -472,8 +488,9 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                     NotificationChannel channel = (NotificationChannel) args.arg2;
+                    RankingMap ranking = (RankingMap) args.arg3;
                     args.recycle();
-                    Adjustment adjustment = onNotificationEnqueued(sbn, channel);
+                    Adjustment adjustment = onNotificationEnqueued(sbn, channel, ranking);
                     setAdjustmentIssuer(adjustment);
                     if (adjustment != null) {
                         if (!isBound()) {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c41e599..64cddc3 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1431,7 +1431,8 @@
 
         @Override
         public void onNotificationEnqueuedWithChannel(
-                IStatusBarNotificationHolder notificationHolder, NotificationChannel channel)
+                IStatusBarNotificationHolder notificationHolder, NotificationChannel channel,
+                NotificationRankingUpdate update)
                 throws RemoteException {
             // no-op in the listener
         }
diff --git a/core/java/android/service/smartspace/ISmartspaceService.aidl b/core/java/android/service/smartspace/ISmartspaceService.aidl
new file mode 100644
index 0000000..c9c6807
--- /dev/null
+++ b/core/java/android/service/smartspace/ISmartspaceService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to Smartspace service.
+ *
+ * @hide
+ */
+oneway interface ISmartspaceService {
+
+    void onCreateSmartspaceSession(in SmartspaceConfig context, in SmartspaceSessionId sessionId);
+
+    void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+    void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+    void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void onDestroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/service/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
new file mode 100644
index 0000000..09b7310
--- /dev/null
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -0,0 +1,313 @@
+/*
+ * 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.service.smartspace;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService.Stub;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A service used to share the lifecycle of smartspace UI (open, close, interaction)
+ * and also to return smartspace result on a query.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SmartspaceService extends Service {
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>The service must also require the {@link android.permission#MANAGE_SMARTSPACE}
+     * permission.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.smartspace.SmartspaceService";
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SmartspaceService";
+    private final ArrayMap<SmartspaceSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+            new ArrayMap<>();
+    private Handler mHandler;
+
+    private final android.service.smartspace.ISmartspaceService mInterface = new Stub() {
+
+        @Override
+        public void onCreateSmartspaceSession(SmartspaceConfig smartspaceConfig,
+                SmartspaceSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doCreateSmartspaceSession,
+                            SmartspaceService.this, smartspaceConfig, sessionId));
+        }
+
+        @Override
+        public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+                SmartspaceTargetEvent event) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::notifySmartspaceEvent,
+                            SmartspaceService.this, sessionId, event));
+        }
+
+        @Override
+        public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doRequestPredictionUpdate,
+                            SmartspaceService.this, sessionId));
+        }
+
+        @Override
+        public void registerSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doRegisterSmartspaceUpdates,
+                            SmartspaceService.this, sessionId, callback));
+        }
+
+        @Override
+        public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doUnregisterSmartspaceUpdates,
+                            SmartspaceService.this, sessionId, callback));
+        }
+
+        @Override
+        public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doDestroy,
+                            SmartspaceService.this, sessionId));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    @NonNull
+    public final IBinder onBind(@NonNull Intent intent) {
+        Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Slog.w(TAG, "Tried to bind to wrong intent (should be "
+                + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+            @NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        mSessionCallbacks.put(sessionId, new ArrayList<>());
+        onCreateSmartspaceSession(config, sessionId);
+    }
+
+    /**
+     * Gets called when the client calls <code> SmartspaceManager#createSmartspaceSession </code>.
+     */
+    public abstract void onCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+            @NonNull SmartspaceSessionId sessionId);
+
+    /**
+     * Gets called when the client calls <code> SmartspaceSession#notifySmartspaceEvent </code>.
+     */
+    @MainThread
+    public abstract void notifySmartspaceEvent(@NonNull SmartspaceSessionId sessionId,
+            @NonNull SmartspaceTargetEvent event);
+
+    /**
+     * Gets called when the client calls <code> SmartspaceSession#requestSmartspaceUpdate </code>.
+     */
+    @MainThread
+    public abstract void onRequestSmartspaceUpdate(@NonNull SmartspaceSessionId sessionId);
+
+    private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper == null) {
+            callbacks.add(new CallbackWrapper(callback,
+                    callbackWrapper ->
+                            mHandler.post(
+                                    () -> removeCallbackWrapper(callbacks, callbackWrapper))));
+        }
+    }
+
+    private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper != null) {
+            removeCallbackWrapper(callbacks, wrapper);
+        }
+    }
+
+    private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+        // Just an optimization, if there are no callbacks, then don't bother notifying the service
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null && !callbacks.isEmpty()) {
+            onRequestSmartspaceUpdate(sessionId);
+        }
+    }
+
+    /**
+     * Finds the callback wrapper for the given callback.
+     */
+    private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+            ISmartspaceCallback callback) {
+        for (int i = callbacks.size() - 1; i >= 0; i--) {
+            if (callbacks.get(i).isCallback(callback)) {
+                return callbacks.get(i);
+            }
+        }
+        return null;
+    }
+
+    private void removeCallbackWrapper(
+            ArrayList<CallbackWrapper> callbacks, CallbackWrapper wrapper) {
+        if (callbacks == null) {
+            return;
+        }
+        callbacks.remove(wrapper);
+    }
+
+    /**
+     * Gets called when the client calls <code> SmartspaceManager#destroy() </code>.
+     */
+    public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
+
+    private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+        super.onDestroy();
+        mSessionCallbacks.remove(sessionId);
+        onDestroySmartspaceSession(sessionId);
+    }
+
+    /**
+     * Used by the prediction factory to send back results the client app. The can be called
+     * in response to {@link #onRequestSmartspaceUpdate(SmartspaceSessionId)} or proactively as
+     * a result of changes in predictions.
+     */
+    public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
+            @NonNull List<SmartspaceTarget> targets) {
+        Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+        List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null) {
+            for (CallbackWrapper callback : callbacks) {
+                callback.accept(targets);
+            }
+        }
+    }
+
+    /**
+     * Destroys a smartspace session.
+     */
+    @MainThread
+    public abstract void onDestroy(@NonNull SmartspaceSessionId sessionId);
+
+    private static final class CallbackWrapper implements Consumer<List<SmartspaceTarget>>,
+            IBinder.DeathRecipient {
+
+        private final Consumer<CallbackWrapper> mOnBinderDied;
+        private ISmartspaceCallback mCallback;
+
+        CallbackWrapper(ISmartspaceCallback callback,
+                @Nullable Consumer<CallbackWrapper> onBinderDied) {
+            mCallback = callback;
+            mOnBinderDied = onBinderDied;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to link to death: " + e);
+            }
+        }
+
+        public boolean isCallback(@NonNull ISmartspaceCallback callback) {
+            if (mCallback == null) {
+                Slog.e(TAG, "Callback is null, likely the binder has died.");
+                return false;
+            }
+            return mCallback.equals(callback);
+        }
+
+        @Override
+        public void accept(List<SmartspaceTarget> smartspaceTargets) {
+            try {
+                if (mCallback != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "CallbackWrapper.accept smartspaceTargets=" + smartspaceTargets);
+                    }
+                    mCallback.onResult(new ParceledListSlice(smartspaceTargets));
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result:" + e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+            mCallback = null;
+            if (mOnBinderDied != null) {
+                mOnBinderDied.accept(this);
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 68d6f3f..25f8090 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,7 +345,8 @@
      */
     @SystemApi
     @HotwordConfigResult
-    public final int setHotwordDetectionConfig(@Nullable Bundle options) {
+    public final int setHotwordDetectionConfig(
+            @SuppressLint("NullableCollection") @Nullable Bundle options) {
         if (mSystemService == null) {
             throw new IllegalStateException("Not available until onReady() is called");
         }
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index 723f1dd..49ff237 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -28,6 +28,8 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
 import com.android.internal.view.IInputMethodManager;
 
 import java.io.PrintWriter;
@@ -91,7 +93,9 @@
      * @param where
      */
     public void sendToService(byte[] protoDump, int source, String where) throws RemoteException {
-        mService.startProtoDump(protoDump, source, where);
+        final Completable.Void value = Completable.createVoid();
+        mService.startProtoDump(protoDump, source, where, ResultCallbacks.of(value));
+        Completable.getResult(value);
     }
 
     /**
diff --git a/core/java/android/util/imetracing/OWNERS b/core/java/android/util/imetracing/OWNERS
new file mode 100644
index 0000000..885fd0a
--- /dev/null
+++ b/core/java/android/util/imetracing/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/view/ContentInfo.java b/core/java/android/view/ContentInfo.java
index bc66ea1..547bc9d 100644
--- a/core/java/android/view/ContentInfo.java
+++ b/core/java/android/view/ContentInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -204,6 +205,7 @@
      * the IME.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
@@ -347,7 +349,7 @@
          * @return this builder
          */
         @NonNull
-        public Builder setExtras(@Nullable Bundle extras) {
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f4b90e1..bc03222 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.Battery;
 import android.hardware.SensorManager;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
@@ -73,6 +74,7 @@
     private final boolean mHasMicrophone;
     private final boolean mHasButtonUnderPad;
     private final boolean mHasSensor;
+    private final boolean mHasBattery;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
     @GuardedBy("mMotionRanges")
@@ -84,6 +86,9 @@
     @GuardedBy("mMotionRanges")
     private SensorManager mSensorManager;
 
+    @GuardedBy("mMotionRanges")
+    private Battery mBattery;
+
     /**
      * A mask for input source classes.
      *
@@ -323,6 +328,13 @@
     public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
 
     /**
+     * The input source is a sensor associated with the input device.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_SENSOR = 0x04000000 | SOURCE_CLASS_NONE;
+
+    /**
      * A special input source constant that is used when filtering input devices
      * to match devices that provide any type of input source.
      */
@@ -448,7 +460,7 @@
     public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
             int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
             KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
-            boolean hasButtonUnderPad, boolean hasSensor) {
+            boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery) {
         mId = id;
         mGeneration = generation;
         mControllerNumber = controllerNumber;
@@ -464,6 +476,7 @@
         mHasMicrophone = hasMicrophone;
         mHasButtonUnderPad = hasButtonUnderPad;
         mHasSensor = hasSensor;
+        mHasBattery = hasBattery;
         mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
     }
 
@@ -483,6 +496,7 @@
         mHasMicrophone = in.readInt() != 0;
         mHasButtonUnderPad = in.readInt() != 0;
         mHasSensor = in.readInt() != 0;
+        mHasBattery = in.readInt() != 0;
         mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
 
         int numRanges = in.readInt();
@@ -830,6 +844,22 @@
     }
 
     /**
+     * Gets the battery object associated with the device, if there is one.
+     * Even if the device does not have a battery, the result is never null.
+     * Use {@link Battery#hasBattery} to determine whether a battery is
+     * present.
+     *
+     * @return The battery object associated with the device, never null.
+     */
+    @NonNull
+    public Battery getBattery() {
+        if (mBattery == null) {
+            mBattery = InputManager.getInstance().getInputDeviceBattery(mId, mHasBattery);
+        }
+        return mBattery;
+    }
+
+    /**
      * Gets the sensor manager service associated with the input device.
      * Even if the device does not have a sensor, the result is never null.
      * Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
@@ -1051,6 +1081,7 @@
         out.writeInt(mHasMicrophone ? 1 : 0);
         out.writeInt(mHasButtonUnderPad ? 1 : 0);
         out.writeInt(mHasSensor ? 1 : 0);
+        out.writeInt(mHasBattery ? 1 : 0);
 
         final int numRanges = mMotionRanges.size();
         out.writeInt(numRanges);
@@ -1097,6 +1128,8 @@
 
         description.append("  Has Sensor: ").append(mHasSensor).append("\n");
 
+        description.append("  Has battery: ").append(mHasBattery).append("\n");
+
         description.append("  Has mic: ").append(mHasMicrophone).append("\n");
 
         description.append("  Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bf377b0..d68e903 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -106,7 +106,9 @@
     public static final int ITYPE_NAVIGATION_BAR = 1;
     public static final int ITYPE_CAPTION_BAR = 2;
 
-    public static final int ITYPE_TOP_GESTURES = 3;
+    // The always visible types are visible to all windows regardless of the z-order.
+    public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3;
+    public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE;
     public static final int ITYPE_BOTTOM_GESTURES = 4;
     public static final int ITYPE_LEFT_GESTURES = 5;
     public static final int ITYPE_RIGHT_GESTURES = 6;
@@ -117,15 +119,16 @@
     public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
     public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
 
-    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 11;
-    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 12;
-    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 13;
-    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 14;
+    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
+    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
+    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
+    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
+    public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT;
 
-    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 15;
-    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 16;
-    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 17;
-    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 18;
+    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
+    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
+    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
+    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
 
     /** Input method window. */
     public static final int ITYPE_IME = 19;
@@ -182,6 +185,18 @@
     }
 
     /**
+     * Mirror the always visible sources from the other state. They will share the same object for
+     * the always visible types.
+     *
+     * @param other the state to mirror the mirrored sources from.
+     */
+    public void mirrorAlwaysVisibleInsetsSources(InsetsState other) {
+        for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) {
+            mSources[type] = other.mSources[type];
+        }
+    }
+
+    /**
      * Calculates {@link WindowInsets} based on the current source configuration.
      *
      * @param frame The frame to calculate the insets relative to.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0a1a231..acd2507 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -750,18 +750,22 @@
     private abstract static class CaptureArgs {
         private final int mPixelFormat;
         private final Rect mSourceCrop = new Rect();
-        private final float mFrameScale;
+        private final float mFrameScaleX;
+        private final float mFrameScaleY;
         private final boolean mCaptureSecureLayers;
         private final boolean mAllowProtected;
         private final long mUid;
+        private final boolean mGrayscale;
 
         private CaptureArgs(Builder<? extends Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
             mSourceCrop.set(builder.mSourceCrop);
-            mFrameScale = builder.mFrameScale;
+            mFrameScaleX = builder.mFrameScaleX;
+            mFrameScaleY = builder.mFrameScaleY;
             mCaptureSecureLayers = builder.mCaptureSecureLayers;
             mAllowProtected = builder.mAllowProtected;
             mUid = builder.mUid;
+            mGrayscale = builder.mGrayscale;
         }
 
         /**
@@ -772,10 +776,12 @@
         abstract static class Builder<T extends Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
-            private float mFrameScale = 1;
+            private float mFrameScaleX = 1;
+            private float mFrameScaleY = 1;
             private boolean mCaptureSecureLayers;
             private boolean mAllowProtected;
             private long mUid = -1;
+            private boolean mGrayscale;
 
             /**
              * The desired pixel format of the returned buffer.
@@ -798,7 +804,18 @@
              * The desired scale of the returned buffer. The raw screen will be scaled up/down.
              */
             public T setFrameScale(float frameScale) {
-                mFrameScale = frameScale;
+                mFrameScaleX = frameScale;
+                mFrameScaleY = frameScale;
+                return getThis();
+            }
+
+            /**
+             * The desired scale of the returned buffer, allowing separate values for x and y scale.
+             * The raw screen will be scaled up/down.
+             */
+            public T setFrameScale(float frameScaleX, float frameScaleY) {
+                mFrameScaleX = frameScaleX;
+                mFrameScaleY = frameScaleY;
                 return getThis();
             }
 
@@ -834,6 +851,14 @@
             }
 
             /**
+             * Set whether the screenshot should use grayscale or not.
+             */
+            public T setGrayscale(boolean grayscale) {
+                mGrayscale = grayscale;
+                return getThis();
+            }
+
+            /**
              * Each sub class should return itself to allow the builder to chain properly
              */
             abstract T getThis();
@@ -929,7 +954,7 @@
     /**
      * The arguments class used to make layer capture requests.
      *
-     * @see #nativeCaptureLayers(LayerCaptureArgs)
+     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
      * @hide
      */
     public static class LayerCaptureArgs extends CaptureArgs {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c0e156..9a412fc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -41,6 +41,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UiThread;
@@ -9030,7 +9031,8 @@
      *                  not be null or empty if a non-null listener is passed in.
      * @param listener The listener to use. This can be null to reset to the default behavior.
      */
-    public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
+    public void setOnReceiveContentListener(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes,
             @Nullable OnReceiveContentListener listener) {
         if (listener != null) {
             Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
@@ -9106,6 +9108,7 @@
      * @return The MIME types accepted by {@link #performReceiveContent} for this view (may
      * include patterns such as "image/*").
      */
+    @SuppressLint("NullableCollection")
     @Nullable
     public String[] getOnReceiveContentMimeTypes() {
         return mOnReceiveContentMimeTypes;
@@ -21367,6 +21370,10 @@
             int height = mBottom - mTop;
             int layerType = getLayerType();
 
+            // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+            // instead of being "stateful" like other RenderNode properties
+            renderNode.clearStretch();
+
             final RecordingCanvas canvas = renderNode.beginRecording(width, height);
 
             try {
@@ -22793,6 +22800,11 @@
         final Rect bounds = drawable.getBounds();
         final int width = bounds.width();
         final int height = bounds.height();
+
+        // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+        // instead of being "stateful" like other RenderNode properties
+        renderNode.clearStretch();
+
         final RecordingCanvas canvas = renderNode.beginRecording(width, height);
 
         // Reverse left/top translation done by drawable canvas, which will
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index f5aa97a..8b3fb2e 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -377,7 +378,8 @@
      * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
      * will be ignored when used for Assist.
      */
-    public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) {}
+    public void setOnReceiveContentMimeTypes(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes) {}
 
     /**
      * Sets the {@link android.text.InputType} bits of this node.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fa471fa..8319b74 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@
 import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -99,6 +100,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -2855,12 +2857,14 @@
 
         /**
          * The token of {@link android.app.WindowContext}. It is usually a
-         * {@link android.app.WindowTokenClient} and is used for updating
-         * {@link android.content.res.Resources} from {@link Configuration} propagated from the
-         * server side.
+         * {@link android.app.WindowTokenClient} and is used for associating the params with an
+         * existing node in the WindowManager hierarchy and getting the corresponding
+         * {@link Configuration} and {@link android.content.res.Resources} values with updates
+         * propagated from the server side.
          *
          * @hide
          */
+        @Nullable
         public IBinder mWindowContextToken = null;
 
         /**
@@ -3547,6 +3551,37 @@
             return userActivityTimeout;
         }
 
+        /**
+         * Sets the {@link android.app.WindowContext} token.
+         *
+         * @see #getWindowContextToken()
+         *
+         * @hide
+         */
+        @TestApi
+        public final void setWindowContextToken(@NonNull IBinder token) {
+            mWindowContextToken = token;
+        }
+
+        /**
+         * Gets the {@link android.app.WindowContext} token.
+         *
+         * The token is usually a {@link android.app.WindowTokenClient} and is used for associating
+         * the params with an existing node in the WindowManager hierarchy and getting the
+         * corresponding {@link Configuration} and {@link android.content.res.Resources} values with
+         * updates propagated from the server side.
+         *
+         * @see android.app.WindowTokenClient
+         * @see Context#createWindowContext(Display, int, Bundle)
+         *
+         * @hide
+         */
+        @TestApi
+        @Nullable
+        public final IBinder getWindowContextToken() {
+            return mWindowContextToken;
+        }
+
         public int describeContents() {
             return 0;
         }
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a76d46d1..f3111bd 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -858,7 +858,7 @@
     boolean reportFullscreenMode(boolean enabled);
 
     /**
-     * Have the editor perform spell checking around the current selection.
+     * Have the editor perform spell checking for the full content.
      *
      * <p>The editor can ignore this method call if it does not support spell checking.
      *
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 0a1aea3..5980cb6 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -187,7 +187,8 @@
      * @return The spell checker session of the spell checker.
      */
     @Nullable
-    public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle,
+    public SpellCheckerSession newSpellCheckerSession(
+            @SuppressLint("NullableCollection") @Nullable Bundle bundle,
             @SuppressLint("UseIcu") @Nullable Locale locale,
             @NonNull SpellCheckerSessionListener listener,
             @SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings,
@@ -277,6 +278,7 @@
      * @return The list of currently enabled spell checkers.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<SpellCheckerInfo> getEnabledSpellCheckersList() {
         final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
         return enabledSpellCheckers != null ? Arrays.asList(enabledSpellCheckers) : null;
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index ffdb89d..98738ef 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -25,8 +27,10 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
 import android.view.View;
 import android.widget.RemoteViews.RemoteView;
 
@@ -42,25 +46,32 @@
  * @attr ref android.R.styleable#AnalogClock_dial
  * @attr ref android.R.styleable#AnalogClock_hand_hour
  * @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @attr ref android.R.styleable#AnalogClock_hand_second
  * @deprecated This widget is no longer supported.
  */
 @RemoteView
 @Deprecated
 public class AnalogClock extends View {
+    /** How often the clock should refresh to make the seconds hand advance at ~15 FPS. */
+    private static final long SECONDS_TICK_FREQUENCY_MS = 1000 / 15;
+
     private Clock mClock;
 
     @UnsupportedAppUsage
     private Drawable mHourHand;
     @UnsupportedAppUsage
     private Drawable mMinuteHand;
+    @Nullable
+    private Drawable mSecondHand;
     @UnsupportedAppUsage
     private Drawable mDial;
 
     private int mDialWidth;
     private int mDialHeight;
 
-    private boolean mAttached;
+    private boolean mVisible;
 
+    private float mSeconds;
     private float mMinutes;
     private float mHour;
     private boolean mChanged;
@@ -101,18 +112,70 @@
             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
         }
 
+        mSecondHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_second);
+
         mClock = Clock.systemDefaultZone();
 
         mDialWidth = mDial.getIntrinsicWidth();
         mDialHeight = mDial.getIntrinsicHeight();
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
+    /** Sets the dial of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setDial(@NonNull Icon icon) {
+        mDial = icon.loadDrawable(getContext());
+        mDialWidth = mDial.getIntrinsicWidth();
+        mDialHeight = mDial.getIntrinsicHeight();
 
-        if (!mAttached) {
-            mAttached = true;
+        mChanged = true;
+        invalidate();
+    }
+
+    /** Sets the hour hand of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setHourHand(@NonNull Icon icon) {
+        mHourHand = icon.loadDrawable(getContext());
+
+        mChanged = true;
+        invalidate();
+    }
+
+    /** Sets the minute hand of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setMinuteHand(@NonNull Icon icon) {
+        mMinuteHand = icon.loadDrawable(getContext());
+
+        mChanged = true;
+        invalidate();
+    }
+
+    /**
+     * Sets the second hand of the clock to the specified Icon, or hides the second hand if it is
+     * null.
+     */
+    @RemotableViewMethod
+    public void setSecondHand(@Nullable Icon icon) {
+        mSecondHand = icon == null ? null : icon.loadDrawable(getContext());
+        mSecondsTick.run();
+
+        mChanged = true;
+        invalidate();
+    }
+
+    @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+
+        if (isVisible) {
+            onVisible();
+        } else {
+            onInvisible();
+        }
+    }
+
+    private void onVisible() {
+        if (!mVisible) {
+            mVisible = true;
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(Intent.ACTION_TIME_TICK);
@@ -128,6 +191,8 @@
             // user not the one the context is for.
             getContext().registerReceiverAsUser(mIntentReceiver,
                     android.os.Process.myUserHandle(), filter, null, getHandler());
+
+            mSecondsTick.run();
         }
 
         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
@@ -140,12 +205,11 @@
         onTimeChanged();
     }
 
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (mAttached) {
+    private void onInvisible() {
+        if (mVisible) {
             getContext().unregisterReceiver(mIntentReceiver);
-            mAttached = false;
+            removeCallbacks(mSecondsTick);
+            mVisible = false;
         }
     }
 
@@ -237,6 +301,20 @@
         minuteHand.draw(canvas);
         canvas.restore();
 
+        final Drawable secondHand = mSecondHand;
+        if (secondHand != null) {
+            canvas.save();
+            canvas.rotate(mSeconds / 60.0f * 360.0f, x, y);
+
+            if (changed) {
+                w = secondHand.getIntrinsicWidth();
+                h = secondHand.getIntrinsicHeight();
+                secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+            }
+            secondHand.draw(canvas);
+            canvas.restore();
+        }
+
         if (scaled) {
             canvas.restore();
         }
@@ -250,6 +328,7 @@
         int minute = localDateTime.getMinute();
         int second = localDateTime.getSecond();
 
+        mSeconds = second + localDateTime.getNano() / 1_000_000_000f;
         mMinutes = minute + second / 60.0f;
         mHour = hour + mMinutes / 60.0f;
         mChanged = true;
@@ -271,6 +350,21 @@
         }
     };
 
+    private final Runnable mSecondsTick = new Runnable() {
+        @Override
+        public void run() {
+            if (!mVisible || mSecondHand == null) {
+                return;
+            }
+
+            onTimeChanged();
+
+            invalidate();
+
+            postDelayed(this, SECONDS_TICK_FREQUENCY_MS);
+        }
+    };
+
     private void updateContentDescription(long timeMillis) {
         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
         String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 97d98fd..794b642 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -220,29 +220,26 @@
     }
 
     void onPerformSpellCheck() {
-        final int selectionStart = mTextView.getSelectionStart();
-        final int selectionEnd = mTextView.getSelectionEnd();
-        final int selectionRangeStart;
-        final int selectionRangeEnd;
-        if (selectionStart < selectionEnd) {
-            selectionRangeStart = selectionStart;
-            selectionRangeEnd = selectionEnd;
-        } else {
-            selectionRangeStart = selectionEnd;
-            selectionRangeEnd = selectionStart;
-        }
-        // Expand the range so that it (hopefully) includes the current sentence.
-        final int start = Math.max(0, selectionRangeStart - MIN_SENTENCE_LENGTH);
-        final int end = Math.min(mTextView.length(), selectionRangeEnd + MIN_SENTENCE_LENGTH);
+        // Triggers full content spell check.
+        final int start = 0;
+        final int end = mTextView.length();
         if (DBG) {
             Log.d(TAG, "performSpellCheckAroundSelection: " + start + ", " + end);
         }
-        spellCheck(start, end);
+        spellCheck(start, end, /* forceCheckWhenEditingWord= */ true);
     }
 
     public void spellCheck(int start, int end) {
+        spellCheck(start, end, /* forceCheckWhenEditingWord= */ false);
+    }
+
+    /**
+     * Requests to do spell check for text in the range (start, end).
+     */
+    public void spellCheck(int start, int end, boolean forceCheckWhenEditingWord) {
         if (DBG) {
-            Log.d(TAG, "Start spell-checking: " + start + ", " + end);
+            Log.d(TAG, "Start spell-checking: " + start + ", " + end + ", "
+                    + forceCheckWhenEditingWord);
         }
         final Locale locale = mTextView.getSpellCheckerLocale();
         final boolean isSessionActive = isSessionActive();
@@ -267,7 +264,7 @@
         for (int i = 0; i < length; i++) {
             final SpellParser spellParser = mSpellParsers[i];
             if (spellParser.isFinished()) {
-                spellParser.parse(start, end);
+                spellParser.parse(start, end, forceCheckWhenEditingWord);
                 return;
             }
         }
@@ -282,10 +279,14 @@
 
         SpellParser spellParser = new SpellParser();
         mSpellParsers[length] = spellParser;
-        spellParser.parse(start, end);
+        spellParser.parse(start, end, forceCheckWhenEditingWord);
     }
 
     private void spellCheck() {
+        spellCheck(/* forceCheckWhenEditingWord= */ false);
+    }
+
+    private void spellCheck(boolean forceCheckWhenEditingWord) {
         if (mSpellCheckerSession == null) return;
 
         Editable editable = (Editable) mTextView.getText();
@@ -295,6 +296,12 @@
         TextInfo[] textInfos = new TextInfo[mLength];
         int textInfosCount = 0;
 
+        if (DBG) {
+            Log.d(TAG, "forceCheckWhenEditingWord=" + forceCheckWhenEditingWord
+                    + ", mLength=" + mLength + ", cookie = " + mCookie
+                    + ", sel start = " + selectionStart + ", sel end = " + selectionEnd);
+        }
+
         for (int i = 0; i < mLength; i++) {
             final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
             if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
@@ -319,7 +326,7 @@
             } else {
                 isEditing = selectionEnd < start || selectionStart > end;
             }
-            if (start >= 0 && end > start && isEditing) {
+            if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
                 spellCheckSpan.setSpellCheckInProgress(true);
                 final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
                 textInfos[textInfosCount++] = textInfo;
@@ -546,7 +553,11 @@
     private class SpellParser {
         private Object mRange = new Object();
 
-        public void parse(int start, int end) {
+        // Forces to do spell checker even user is editing the word.
+        private boolean mForceCheckWhenEditingWord;
+
+        public void parse(int start, int end, boolean forceCheckWhenEditingWord) {
+            mForceCheckWhenEditingWord = forceCheckWhenEditingWord;
             final int max = mTextView.length();
             final int parseEnd;
             if (end > max) {
@@ -567,6 +578,7 @@
 
         public void stop() {
             removeRangeSpan((Editable) mTextView.getText());
+            mForceCheckWhenEditingWord = false;
         }
 
         private void setRangeSpan(Editable editable, int start, int end) {
@@ -617,7 +629,7 @@
                 if (DBG) {
                     Log.i(TAG, "No more spell check.");
                 }
-                removeRangeSpan(editable);
+                stop();
                 return;
             }
 
@@ -649,7 +661,7 @@
                     if (DBG) {
                         Log.i(TAG, "Incorrect range span.");
                     }
-                    removeRangeSpan(editable);
+                    stop();
                     return;
                 }
                 do {
@@ -778,7 +790,7 @@
                 removeRangeSpan(editable);
             }
 
-            spellCheck();
+            spellCheck(mForceCheckWhenEditingWord);
         }
 
         private <T> void removeSpansAt(Editable editable, int offset, T[] spans) {
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index f29eb39..cdb4762 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.os.IBinder;
@@ -151,6 +152,7 @@
     /** Gets direct child tasks (ordered from top-to-bottom) */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<ActivityManager.RunningTaskInfo> getChildTasks(
             @NonNull WindowContainerToken parent, @NonNull int[] activityTypes) {
         try {
@@ -163,6 +165,7 @@
     /** Gets all root tasks on a display (ordered from top-to-bottom) */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<ActivityManager.RunningTaskInfo> getRootTasks(
             int displayId, @NonNull int[] activityTypes) {
         try {
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 99692d0..7ade05c 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -5,3 +5,4 @@
 per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
 per-file IVoice* = file:/core/java/android/service/voice/OWNERS
 per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
+per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 13358daf..ee98878 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -62,6 +62,11 @@
      */
     public static final String ENABLE_NAS_FEEDBACK = "enable_nas_feedback";
 
+    /**
+     * Whether the Notification Assistant can label a notification not a conversation
+     */
+    public static final String ENABLE_NAS_NOT_CONVERSATION = "enable_nas_not_conversation";
+
     // Flags related to screenshot intelligence
 
     /**
@@ -420,6 +425,12 @@
     public static final String PIP_STASHING = "pip_stashing";
 
     /**
+     * (float) The threshold velocity to cause PiP to be stashed when flinging from one edge to the
+     * other.
+     */
+    public static final String PIP_STASH_MINIMUM_VELOCITY_THRESHOLD = "pip_velocity_threshold";
+
+    /**
      * (float) Bottom height in DP for Back Gesture.
      */
     public static final String BACK_GESTURE_BOTTOM_HEIGHT = "back_gesture_bottom_height";
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index b8c066d..ea2fb88 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -57,7 +56,7 @@
                 mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
-                if (!isWifiOnlyDevice(mContext)) {
+                if (!BatteryStatsHelper.checkWifiOnly(mContext)) {
                     mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
                 }
                 mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
@@ -81,14 +80,6 @@
         return mPowerCalculators;
     }
 
-    private static boolean isWifiOnlyDevice(Context context) {
-        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-        if (cm == null) {
-            return false;
-        }
-        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
-    }
-
     /**
      * Returns a snapshot of battery attribution data.
      */
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 892c5a5..50bbfc5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -91,11 +91,12 @@
     /** Remove the IME surface. Requires passing the currently focused window. */
     oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
             in IVoidResultCallback resultCallback);
-    void startProtoDump(in byte[] protoDump, int source, String where);
+    oneway void startProtoDump(in byte[] protoDump, int source, String where,
+            in IVoidResultCallback resultCallback);
     oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
 
     // Starts an ime trace.
-    void startImeTrace();
+    oneway void startImeTrace(in IVoidResultCallback resultCallback);
     // Stops an ime trace.
-    void stopImeTrace();
+    oneway void stopImeTrace(in IVoidResultCallback resultCallback);
 }
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 4eaa016..9cc7243 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,7 +70,8 @@
                                           deviceInfo.isExternal(), deviceInfo.getSources(),
                                           deviceInfo.getKeyboardType(), kcmObj.get(),
                                           deviceInfo.hasVibrator(), hasMic,
-                                          deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor()));
+                                          deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
+                                          deviceInfo.hasBattery()));
 
     const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -90,9 +91,10 @@
     gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
     gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
 
-    gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
-                                                  "(IIILjava/lang/String;IILjava/lang/"
-                                                  "String;ZIILandroid/view/KeyCharacterMap;ZZZZ)V");
+    gInputDeviceClassInfo.ctor =
+            GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
+                             "(IIILjava/lang/String;IILjava/lang/"
+                             "String;ZIILandroid/view/KeyCharacterMap;ZZZZZ)V");
 
     gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFFF)V");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4ef63ae..05fcaec 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
 #include <android-base/chrono_utils.h>
 #include <android/graphics/region.h>
 #include <android/gui/BnScreenCaptureListener.h>
+#include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 #include <android_runtime/android_view_Surface.h>
@@ -110,10 +111,12 @@
 static struct {
     jfieldID pixelFormat;
     jfieldID sourceCrop;
-    jfieldID frameScale;
+    jfieldID frameScaleX;
+    jfieldID frameScaleY;
     jfieldID captureSecureLayers;
     jfieldID allowProtected;
     jfieldID uid;
+    jfieldID grayscale;
 } gCaptureArgsClassInfo;
 
 static struct {
@@ -380,13 +383,17 @@
     captureArgs.sourceCrop =
             rectFromObj(env,
                         env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop));
-    captureArgs.frameScale =
-            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScale);
+    captureArgs.frameScaleX =
+            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
+    captureArgs.frameScaleY =
+            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY);
     captureArgs.captureSecureLayers =
             env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
     captureArgs.allowProtected =
             env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected);
     captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid);
+    captureArgs.grayscale =
+            env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale);
 }
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -1619,7 +1626,8 @@
                                         jlong frameTimelineVsyncId) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    transaction->setFrameTimelineVsync(frameTimelineVsyncId);
+    transaction->setFrameTimelineInfo(
+            {frameTimelineVsyncId, android::os::IInputConstants::INVALID_INPUT_EVENT_ID});
 }
 
 class JankDataListenerWrapper : public JankDataListener {
@@ -2032,12 +2040,14 @@
     gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
     gCaptureArgsClassInfo.sourceCrop =
             GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
-    gCaptureArgsClassInfo.frameScale = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScale", "F");
+    gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F");
+    gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F");
     gCaptureArgsClassInfo.captureSecureLayers =
             GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
     gCaptureArgsClassInfo.allowProtected =
             GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
     gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
+    gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
 
     jclass displayCaptureArgsClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c76a5d3..c5a9559 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -70,7 +70,6 @@
 #include <bionic/malloc.h>
 #include <bionic/mte.h>
 #include <cutils/fs.h>
-#include <cutils/memory.h>
 #include <cutils/multiuser.h>
 #include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
@@ -621,13 +620,6 @@
 
   // Set the jemalloc decay time to 1.
   mallopt(M_DECAY_TIME, 1);
-
-  // Avoid potentially expensive memory mitigations, mostly meant for system
-  // processes, in apps. These may cause app compat problems, use more memory,
-  // or reduce performance. While it would be nice to have them for apps,
-  // we will have to wait until they are proven out, have more efficient
-  // hardware, and/or apply them only to new applications.
-  process_disable_memory_mitigations();
 }
 
 static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
@@ -1785,6 +1777,14 @@
       break;
   }
   mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
+
+  // Avoid heap zero initialization for applications without MTE. Zero init may
+  // cause app compat problems, use more memory, or reduce performance. While it
+  // would be nice to have them for apps, we will have to wait until they are
+  // proven out, have more efficient hardware, and/or apply them only to new
+  // applications.
+  mallopt(M_BIONIC_ZERO_INIT, 0);
+
   // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
   runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4acdb16..7c1e1b5f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1748,7 +1748,7 @@
      @SystemApi
      <p>Not for use by third-party applications. @hide -->
     <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
-                android:protectionLevel="signature|setup" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows applications to toggle airplane mode.
          <p>Not for use by third-party or privileged applications.
@@ -2708,13 +2708,13 @@
          shown on top of all other apps.
 
          Allows an application to use
-         {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY}
+         {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
          to create overlays that will stay visible, even if another window is requesting overlays to
          be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
 
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
-                android:protectionLevel="signature|wellbeing"/>
+                android:protectionLevel="signature|recents|wellbeing"/>
 
     <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
          @hide
@@ -5168,6 +5168,11 @@
     <permission android:name="android.permission.MANAGE_SEARCH_UI"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the smartspace service.
+     @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_SMARTSPACE"
+        android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -5293,7 +5298,8 @@
                 android:protectionLevel="signature" />
 
     <!-- Allows financial apps to read filtered sms messages.
-         Protection level: signature|appop  -->
+         Protection level: signature|appop
+         @deprecated The API that used this permission is no longer functional.  -->
     <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
         android:protectionLevel="signature|appop" />
 
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8746afa..55eaaf6 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Net Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-rugsteunoproepe"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondes"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4daa9e3..3161226 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi ብቻ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ምትኬ ጥሪ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡አልተላለፈም"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡<xliff:g id="DIALING_NUMBER">{1}</xliff:g> ከ<xliff:g id="TIME_DELAY">{2}</xliff:g> ሰከንዶች በኋላ"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 2f937e0..e91e2b7 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnız Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedək Zəng"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> saniyə sonra"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b3323b3..a490a6c 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> rezervni način za pozivanje"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 173f9b5..20342e0 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Обаждания през друга SIM карта от <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Не е пренасочено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> след <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1c8644e..989e2bb 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – pomoćno pozivanje"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> za <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index d369134..e9c55d7 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Només Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Trucades alternatives (<xliff:g id="SPN">%s</xliff:g>)"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> després de <xliff:g id="TIME_DELAY">{2}</xliff:g> segons"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7936adc..071bbfd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Pouze Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Záložní volání"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ba0ef7f..b760f5b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Kun Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Alternativ løsning til opkald leveret af <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ea56ee2..4f3c8a9 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Μόνο Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Δημιουργία αντιγράφων ασφαλείας κλήσεων"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Δεν προωθήθηκε"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> μετά από <xliff:g id="TIME_DELAY">{2}</xliff:g> δευτερόλεπτα"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4ee4b8e..c5ff193 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamada de copia de seguridad de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> después de <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 39f8ef4f..108c3db 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamadas de reserva de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index faf1b74..70862d4 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Ainult WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – helistamise varuviis"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi pärast"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 9258b30..6a0e454 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏فقط Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> تماس پشتیبان"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> پس از <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانیه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 034ee0b..8e5d9b4 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vain Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Puheluiden varavaihtoehto"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunnin päästä"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 8418604..eadac9e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Só por wifi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non desviada"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> tras <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index f3cf7aa..5f0a8f8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवल वाई-फ़ाई"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> बैक अप कॉलिंग"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंड के बाद"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index aac0650..e1ae1e2 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Rezervni način telefoniranja"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđeno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 841553a..3c04bec 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Csak Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> másodlagos hívási lehetőség"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> másodperc után"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index cddcfe6..bdd2387 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Միայն Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> զանգելու պահեստային տարբերակ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8bd34c3..c84bc13 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Khusus Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Panggilan Cadangan <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> setelah <xliff:g id="TIME_DELAY">{2}</xliff:g> detik"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f9536fe..84611a2 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi eingöngu"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Varasímtöl (<xliff:g id="SPN">%s</xliff:g>)"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> eftir <xliff:g id="TIME_DELAY">{2}</xliff:g> sekúndur"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 409b378..f104d44 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏Wi-Fi בלבד"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"אמצעי גיבוי להתקשרות באמצעות <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ללא העברה"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> כעבור <xliff:g id="TIME_DELAY">{2}</xliff:g> שניות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 0696580..69822ac 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fiのみ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 通話のバックアップ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g> (<xliff:g id="TIME_DELAY">{2}</xliff:g>秒後)"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ae1e6eb..6a7d563 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"მხოლოდ Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> დარეკვის სარეზერვო ხერხი"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: არ არის გადამისამართებული"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> წამის შემდეგ"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index fac878d..d1400b9 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi តែប៉ុណ្ណោះ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"ការហៅទូរសព្ទ​បម្រុង <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> ៖ មិន​បាន​បញ្ជូន​បន្ត"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> បន្ទាប់​ពី <xliff:g id="TIME_DELAY">{2}</xliff:g> វិនាទី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index afeb1bd..32e3c4d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ವೈ-ಫೈ ಮಾತ್ರ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ಬ್ಯಾಕಪ್ ಕರೆ ಮಾಡುವಿಕೆ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index acc5a6a..3407431 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi에서만"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 백업 전화"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안됨"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g><xliff:g id="TIME_DELAY">{2}</xliff:g>초 후"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index d6ff927..9a84d0a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi гана"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Кошумча чалуу ыкмасы"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Багытталган эмес"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секунддан кийин"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 5c65d06..5f72e07 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tik „Wi-Fi“"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – atsarginis skambinimas"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 2840429..37e417f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tikai Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: zvanu rezerves iespēja"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pēc <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundes(-ēm)"</string>
diff --git a/core/res/res/values-mcc310-mnc950-si/strings.xml b/core/res/res/values-mcc310-mnc950-si/strings.xml
deleted file mode 100644
index 26fe4ac..0000000
--- a/core/res/res/values-mcc310-mnc950-si/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2006, 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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="615419724607901560">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
-    <string name="mmcc_illegal_ms" msgid="7801541624846497489">"SIM MM#3 ඉඩ නොදේ"</string>
-    <string name="mmcc_illegal_me" msgid="7066936962628406316">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
-</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 3466bca..5666cc2 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервен начин на повикување преку <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c44adc..891ac47 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Зөвхөн Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Дуудлагыг нөөцлөх"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: дамжуулагдаагүй"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секундын дараа"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 128675e..ec0aa5d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi sahaja"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Panggilan Sandaran"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 62d2d85..7b4a430fc 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ကြိုးမဲ့အင်တာနက် သာလျှင်"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> အရန် ခေါ်ဆိုမှု"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ထပ်ဆင့်မပို့နိုင်ပါ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> နောက် <xliff:g id="TIME_DELAY">{2}</xliff:g> စက္ကန့်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b20111e..4968184 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 9ee5e19..f73b382 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tylko Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Zapasowa metoda wykonywania połączeń <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index ccb7a64..e00034a 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Apenas Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Fazer uma cópia de segurança das chamadas <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index e08cb57..5fd6f1a 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Apelare de rezervă prin <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționată"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> după <xliff:g id="TIME_DELAY">{2}</xliff:g>   secunde"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index f800195..d1cd374 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Только Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервный способ связи: <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переадресовано"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> через <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 322c03c..d102183 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi පමණයි"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> උපස්ථ ඇමතුම"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ඉදිරියට නොයවන ලදි"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: තත්පර <xliff:g id="TIME_DELAY">{2}</xliff:g> ට පසුව <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 7172cab..ffa4b26 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Len Wi‑Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – záložné volanie"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index af03b61..65037a0 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -149,8 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> резервни начин за позивање"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> након <xliff:g id="TIME_DELAY">{2}</xliff:g> секунде/и"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index b87e12b..ef7a102 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Endast Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> används som reserv för samtal"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Vidarebefordras inte"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 397a3b8..cc82f4e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi pekee"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>Kupiga Simu Kupitia Mtandao Mbadala"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijatumiwa mwingine"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> baada ya sekunde <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3c24672..ca90171 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"வைஃபை மட்டும்"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> காப்புப் பிரதி அழைப்பு"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: பகிரப்படவில்லை"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> வினாடிகளுக்குப் பிறகு <xliff:g id="DIALING_NUMBER">{1}</xliff:g> ஐப் பகிர்"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 6012c0a2..5079f23 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi เท่านั้น"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"การสำรองข้อมูลการโทรจาก <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ไม่ได้โอนสาย"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> หลังผ่านไป <xliff:g id="TIME_DELAY">{2}</xliff:g> วินาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 9e677c3..81bfe52 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi lang"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup na Pagtawag"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi naipasa"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pagkatapos ng <xliff:g id="TIME_DELAY">{2}</xliff:g> (na) segundo"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fb23a29..7095bb8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnızca kablosuz"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedek Arama"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> saniye sonra <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ecfcb1b..9864b4b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -150,8 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Лише Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: резервний спосіб здійснення викликів"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переслано"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> після <xliff:g id="TIME_DELAY">{2}</xliff:g> сек."</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 0b48dc1..6612cc9 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Faqat Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Zaxiraviy chaqiruv"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>  <xliff:g id="TIME_DELAY">{2}</xliff:g> soniyadan so‘ng"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 438befd..8f8e0bc 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Chỉ Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Gọi điện dự phòng <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> sau <xliff:g id="TIME_DELAY">{2}</xliff:g> giây"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 20048f1..af5f846 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>备用通话"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g>秒后<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 544cf1e..65586f7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:尚未轉接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> 於 <xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後轉接"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d9b51e0..6facceb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 029c569..3b22e94 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -148,8 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"I-Wi-Fi kuphela"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
-    <skip />
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Ukushaya Ikholi Kwekhopi Yasenqolobaneni"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Akudlulisiwe"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> emuva kwamasekhondi angu-<xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 14f1e0e..07c3adf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4069,6 +4069,7 @@
         <attr name="dial" format="reference"/>
         <attr name="hand_hour" format="reference"/>
         <attr name="hand_minute" format="reference"/>
+        <attr name="hand_second" format="reference"/>
     </declare-styleable>
     <declare-styleable name="Button">
     </declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5e0cda6..01b8efa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3800,6 +3800,15 @@
 -->
     <string name="config_defaultSearchUiService" translatable="false"></string>
 
+    <!-- The package name for the system's smartspace service.
+     This service returns smartspace results.
+
+     This service must be trusted, as it can be activated without explicit consent of the user.
+     If no service with the specified name exists on the device, smartspace will be disabled.
+     Example: "com.android.intelligence/.SmartspaceService"
+-->
+    <string name="config_defaultSmartspaceService" translatable="false"></string>
+
     <!-- The package name for the system's speech recognition service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          Example: "com.android.speech/.RecognitionService"
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9c1c51c..b76ab3a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3059,6 +3059,7 @@
     <public name="pathAdvancedPattern" />
     <public name="sspAdvancedPattern" />
     <public name="fontProviderSystemFontFamily" />
+    <public name="hand_second" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dfccdf4..815330f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3498,6 +3498,7 @@
   <java-symbol type="string" name="config_defaultAppPredictionService" />
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultSearchUiService" />
+  <java-symbol type="string" name="config_defaultSmartspaceService" />
   <java-symbol type="string" name="config_defaultMusicRecognitionService" />
   <java-symbol type="string" name="config_defaultAttentionService" />
   <java-symbol type="string" name="config_defaultRotationResolverService" />
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index da6636b..6c28607 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -16,7 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.graphics.gamemanagertests"
+          package="com.android.app.gamemanagertests"
           android:sharedUserId="android.uid.system" >
 
     <application>
@@ -24,7 +24,7 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.graphics.gamemanagertests"
+                     android:targetPackage="com.android.app.gamemanagertests"
                      android:label="Game Manager Tests"/>
 
 </manifest>
diff --git a/core/tests/GameManagerTests/AndroidTest.xml b/core/tests/GameManagerTests/AndroidTest.xml
index bfb9802..43729923 100644
--- a/core/tests/GameManagerTests/AndroidTest.xml
+++ b/core/tests/GameManagerTests/AndroidTest.xml
@@ -25,7 +25,7 @@
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.graphics.gamemanagertests" />
+        <option name="package" value="com.android.app.gamemanagertests" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false" />
     </test>
diff --git a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
similarity index 95%
rename from core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
rename to core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index d861a89..8f50051 100644
--- a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package android.graphics;
+package android.app;
 
 import static junit.framework.Assert.assertEquals;
 
-import android.graphics.GameManager.GameMode;
+import android.app.GameManager.GameMode;
 import android.util.ArrayMap;
 import android.util.Pair;
 
@@ -31,7 +31,7 @@
 import org.junit.runner.RunWith;
 
 /**
- * Unit tests for {@link GameManager}.
+ * Unit tests for {@link android.app.GameManager}.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index f0b4af6..19458da 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -54,6 +54,32 @@
         assertFalse(Signature.areEffectiveMatch(asArray(A, M), asArray(A, B)));
     }
 
+    public void testHashCode_doesNotIncludeFlags() throws Exception {
+        // Some classes rely on the hash code not including the flags / capabilities for the signer
+        // to verify Set membership. This test verifies two signers with the same signature but
+        // different flags have the same hash code.
+        Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+        // There are currently 5 capabilities that can be assigned to a previous signer, although
+        // for the purposes of this test all that matters is that the two flag values are distinct.
+        signatureAWithAllCaps.setFlags(31);
+        Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+        signatureAWithNoCaps.setFlags(0);
+
+        assertEquals(signatureAWithAllCaps.hashCode(), signatureAWithNoCaps.hashCode());
+    }
+
+    public void testEquals_doesNotIncludeFlags() throws Exception {
+        // Similar to above some classes rely on equals only comparing the signature arrays
+        // for equality without including the flags. This test verifies two signers with the
+        // same signature but different flags are still considered equal.
+        Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+        signatureAWithAllCaps.setFlags(31);
+        Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+        signatureAWithNoCaps.setFlags(0);
+
+        assertEquals(signatureAWithAllCaps, signatureAWithNoCaps);
+    }
+
     private static Signature[] asArray(Signature... s) {
         return s;
     }
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
index b319886..341ee37 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -149,7 +149,7 @@
                 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
                 0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */,
                 false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
-                true /* hasSensor */);
+                true /* hasSensor */, false /* hasBattery */);
         assertTrue(d.hasSensor());
         return d;
     }
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 6955ca8..564103e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -117,6 +117,61 @@
     }
 
     @Test
+    public void testDurationMono() {
+        assertEquals(1, CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(1, 1)).getDuration());
+        assertEquals(-1, CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.createSynced(
+                VibrationEffect.createWaveform(
+                        new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
+    }
+
+    @Test
+    public void testDurationStereo() {
+        assertEquals(6, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.createOneShot(1, 1))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(-1, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+                .combine()
+                .getDuration());
+    }
+
+    @Test
+    public void testDurationSequential() {
+        assertEquals(26, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(10, 10), 10)
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(-1, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+                .combine()
+                .getDuration());
+    }
+
+    @Test
     public void testSerializationMono() {
         CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 301b491..b0ae9b9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,14 +223,6 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
-    <split-permission name="android.permission.RECORD_AUDIO"
-                      targetSdk="31">
-        <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
-    </split-permission>
-    <split-permission name="android.permission.CAMERA"
-                      targetSdk="31">
-        <new-permission name="android.permission.BACKGROUND_CAMERA" />
-    </split-permission>
 
 
     <!-- This is a list of all the libraries available for application
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index c1310a9..4a92cf1 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -693,6 +693,32 @@
         throw new IllegalArgumentException("Unrecognized outline?");
     }
 
+    /** @hide */
+    public boolean clearStretch() {
+        return nClearStretch(mNativeRenderNode);
+    }
+
+    /** @hide */
+    public boolean stretch(float left, float top, float right, float bottom,
+            float vecX, float vecY, float maxStretchAmount) {
+        if (1.0 < vecX || vecX < -1.0) {
+            throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+        }
+        if (1.0 < vecY || vecY < -1.0) {
+            throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+        }
+        if (top <= bottom || right <= left) {
+            throw new IllegalArgumentException(
+                    "Stretch region must not be empty, got "
+                            + new RectF(left, top, right, bottom).toString());
+        }
+        if (maxStretchAmount <= 0.0f) {
+            throw new IllegalArgumentException(
+                    "The max stretch amount must be >0, got " + maxStretchAmount);
+        }
+        return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+    }
+
     /**
      * Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
      * and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
@@ -1638,6 +1664,13 @@
     private static native boolean nSetOutlineNone(long renderNode);
 
     @CriticalNative
+    private static native boolean nClearStretch(long renderNode);
+
+    @CriticalNative
+    private static native boolean nStretch(long renderNode, float left, float top, float right,
+            float bottom, float vecX, float vecY, float maxStretch);
+
+    @CriticalNative
     private static native boolean nHasShadow(long renderNode);
 
     @CriticalNative
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f41b608..ae9f8664 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -19,9 +19,9 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
-import com.android.org.bouncycastle.util.io.pem.PemObject;
-import com.android.org.bouncycastle.util.io.pem.PemReader;
-import com.android.org.bouncycastle.util.io.pem.PemWriter;
+import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
+import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
+import com.android.internal.org.bouncycastle.util.io.pem.PemWriter;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 4a67135..e19d88c 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -45,8 +45,8 @@
 import android.security.keystore.UserNotAuthenticatedException;
 import android.util.Log;
 
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 6ad8d2c..334b111 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -26,24 +26,24 @@
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
 
-import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.ASN1Integer;
-import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import com.android.org.bouncycastle.asn1.DERBitString;
-import com.android.org.bouncycastle.asn1.DERNull;
-import com.android.org.bouncycastle.asn1.DERSequence;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.asn1.x509.Certificate;
-import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
-import com.android.org.bouncycastle.asn1.x509.Time;
-import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import com.android.org.bouncycastle.jce.X509Principal;
-import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.DERBitString;
+import com.android.internal.org.bouncycastle.asn1.DERNull;
+import com.android.internal.org.bouncycastle.asn1.DERSequence;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.internal.org.bouncycastle.asn1.x509.Time;
+import com.android.internal.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.internal.org.bouncycastle.jce.X509Principal;
+import com.android.internal.org.bouncycastle.jce.provider.X509CertificateObject;
+import com.android.internal.org.bouncycastle.x509.X509V3CertificateGenerator;
 
 import libcore.util.EmptyArray;
 
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 164bc86..75ac61a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -363,6 +363,11 @@
             }
         }
 
+        if (response.iSecurityLevel == null) {
+            // This seems to be a pure certificate entry, nothing to return here.
+            return null;
+        }
+
         Integer keymasterAlgorithm = null;
         // We just need one digest for the algorithm name
         int keymasterDigest = -1;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 5e7f648..07169ce 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -490,7 +490,7 @@
             int[] keymasterEncryptionPaddings =
                     KeyProperties.EncryptionPadding.allToKeymaster(
                             spec.getEncryptionPaddings());
-            if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0)
+            if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
                     && (spec.isRandomizedEncryptionRequired())) {
                 for (int keymasterPadding : keymasterEncryptionPaddings) {
                     if (!KeymasterUtils
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 1da72f8..04d1264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -89,6 +89,7 @@
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
                     mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
+                        resetWindowsOffsetInternal(animator.getTransitionDirection());
                         finishOffset(animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
@@ -99,6 +100,7 @@
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
                     mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
+                        resetWindowsOffsetInternal(animator.getTransitionDirection());
                         finishOffset(animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
@@ -205,6 +207,16 @@
         applyTransaction(wct);
     }
 
+    private void resetWindowsOffsetInternal(
+            @OneHandedAnimationController.TransitionDirection int td) {
+        if (td == TRANSITION_DIRECTION_TRIGGER) {
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        resetWindowsOffset(wct);
+        applyTransaction(wct);
+    }
+
     private void resetWindowsOffset(WindowContainerTransaction wct) {
         final SurfaceControl.Transaction tx =
                 mSurfaceControlTransactionFactory.getTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 4b118f1..a57eee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -212,8 +212,8 @@
     }
 
     @Nullable
-    Size getEstimatedMenuSize() {
-        return mPipMenuView == null ? null : mPipMenuView.getEstimatedMenuSize();
+    Size getEstimatedMinMenuSize() {
+        return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 48942b6..2df3e2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -367,15 +367,17 @@
     }
 
     /**
-     * @return estimated {@link Size} for which the width is based on number of actions and
-     *         height based on the height of expand button + top and bottom action bar.
+     * @return Estimated minimum {@link Size} to hold the actions.
+     *         See also {@link #updateActionViews(Rect)}
      */
-    Size getEstimatedMenuSize() {
-        final int pipActionSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.pip_action_size);
-        final int width = mActions.size() * pipActionSize;
-        final int height = pipActionSize * 2 + mContext.getResources().getDimensionPixelSize(
-                R.dimen.pip_expand_action_size);
+    Size getEstimatedMinMenuSize() {
+        final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+        // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+        // on the top action container.
+        final int width = Math.max(2, mActions.size()) * pipActionSize;
+        final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+                + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+                + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
         return new Size(width, height);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 4df7cef..fd4ea61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -27,7 +27,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
-import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
 import android.view.Choreographer;
@@ -62,6 +61,7 @@
 
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
     private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+    private static final int UNSTASH_DURATION = 250;
     private static final int LEAVE_PIP_DURATION = 300;
     private static final int SHIFT_DURATION = 300;
 
@@ -482,6 +482,13 @@
     }
 
     /**
+     * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+     */
+    void animateToUnStashedBounds(Rect unstashedBounds) {
+        resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+    }
+
+    /**
      * Animates the PiP to offset it from the IME or shelf.
      */
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 128d13c..3cb3ae8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -17,7 +17,9 @@
 package com.android.wm.shell.pip.phone;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -30,7 +32,6 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.Size;
@@ -62,9 +63,8 @@
  */
 public class PipTouchHandler {
     private static final String TAG = "PipTouchHandler";
-
-    private static final float STASH_MINIMUM_VELOCITY_X = 3000.f;
     private static final float MINIMUM_SIZE_PERCENT = 0.4f;
+    private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
 
     // Allow PIP to resize to a slightly bigger state upon touch
     private final boolean mEnableResize;
@@ -87,6 +87,8 @@
      */
     private boolean mEnableStash = true;
 
+    private float mStashVelocityThreshold;
+
     // The reference inset bounds, used to determine the dismiss fraction
     private final Rect mInsetBounds = new Rect();
     private int mExpandedShortestEdgeSize;
@@ -176,9 +178,17 @@
         mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
                 mMotionHelper, mainExecutor);
         mTouchState = new PipTouchState(ViewConfiguration.get(context),
-                () -> mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
-                        mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
-                        shouldShowResizeHandle()),
+                () -> {
+                    if (mPipBoundsState.isStashed()) {
+                        animateToUnStashedState();
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    } else {
+                        mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+                                mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+                                willResizeMenu(),
+                                shouldShowResizeHandle());
+                    }
+                },
                 menuController::hideMenu,
                 mainExecutor);
 
@@ -205,6 +215,19 @@
                                 PIP_STASHING, /* defaultValue = */ true);
                     }
                 });
+        mStashVelocityThreshold = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                DEFAULT_STASH_VELOCITY_THRESHOLD);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mainExecutor,
+                properties -> {
+                    if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+                        mStashVelocityThreshold = properties.getFloat(
+                                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                                DEFAULT_STASH_VELOCITY_THRESHOLD);
+                    }
+                });
     }
 
     private void reloadResources() {
@@ -710,6 +733,17 @@
         mSavedSnapFraction = -1f;
     }
 
+    private void animateToUnStashedState() {
+        final Rect pipBounds = mPipBoundsState.getBounds();
+        final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+        final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+        unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+                : mInsetBounds.right - pipBounds.width();
+        unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+                : mInsetBounds.right;
+        mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+    }
+
     /**
      * @return the motion helper.
      */
@@ -773,7 +807,7 @@
             }
 
             if (touchState.startedDragging()) {
-                mPipBoundsState.setStashed(PipBoundsState.STASH_TYPE_NONE);
+                mPipBoundsState.setStashed(STASH_TYPE_NONE);
                 mSavedSnapFraction = -1f;
                 mPipDismissTargetHandler.showDismissTargetMaybe();
             }
@@ -826,14 +860,8 @@
 
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
-                // If user flings the PIP window above the minimum velocity, stash PIP.
-                // Only allow stashing to the edge if the user starts dragging the PIP from that
-                // edge.
                 if (mEnableStash && !mPipBoundsState.isStashed()
-                        && ((vel.x > STASH_MINIMUM_VELOCITY_X
-                        && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
-                        || (vel.x < -STASH_MINIMUM_VELOCITY_X
-                        && mDownSavedFraction > 3f && mDownSavedFraction < 4f))) {
+                        && shouldStash(vel, getPossiblyMotionBounds())) {
                     mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
                 } else {
                     mMotionHelper.flingToSnapTarget(vel.x, vel.y,
@@ -847,24 +875,29 @@
                             && mPipBoundsState.getBounds().height()
                             < mPipBoundsState.getMaxSize().y;
                     if (toExpand) {
+                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                         animateToMaximizedState(null);
                     } else {
-                        animateToMinimizedState();
+                        animateToUnexpandedState(getUserResizeBounds());
                     }
-                    mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                 } else {
                     // Expand to fullscreen if this is a double tap
                     // the PiP should be frozen until the transition ends
                     setTouchEnabled(false);
                     mMotionHelper.expandLeavePip();
                 }
-            } else if (mMenuState != MENU_STATE_FULL && !mPipBoundsState.isStashed()) {
+            } else if (mMenuState != MENU_STATE_FULL) {
                 if (!mTouchState.isWaitingForDoubleTap()) {
-                    // User has stalled long enough for this not to be a drag or a double tap, just
-                    // expand the menu
-                    mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
-                            true /* allowMenuTimeout */, willResizeMenu(),
-                            shouldShowResizeHandle());
+                    if (mPipBoundsState.isStashed()) {
+                        animateToUnStashedState();
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    } else {
+                        // User has stalled long enough for this not to be a drag or a double tap,
+                        // just expand the menu
+                        mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                                true /* allowMenuTimeout */, willResizeMenu(),
+                                shouldShowResizeHandle());
+                    }
                 } else {
                     // Next touch event _may_ be the second tap for the double-tap, schedule a
                     // fallback runnable to trigger the menu if no touch event occurs before the
@@ -895,6 +928,26 @@
                 mPipExclusionBoundsChangeListener.get().accept(new Rect());
             }
         }
+
+        private boolean shouldStash(PointF vel, Rect motionBounds) {
+            // If user flings the PIP window above the minimum velocity, stash PIP.
+            // Only allow stashing to the edge if the user starts dragging the PIP from the
+            // opposite edge.
+            final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
+                    && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+                    || (vel.x > mStashVelocityThreshold
+                    && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+
+            // If User releases the PIP window while it's out of the display bounds, put
+            // PIP into stashed mode.
+            final int offset = motionBounds.width() / 2;
+            final boolean stashFromDroppingOnEdge =
+                    (motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset
+                            || motionBounds.left
+                            < mPipBoundsState.getDisplayBounds().left - offset);
+
+            return stashFromFlingToEdge || stashFromDroppingOnEdge;
+        }
     }
 
     void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
@@ -932,14 +985,14 @@
         if (!mEnableResize) {
             return false;
         }
-        final Size estimatedMenuSize = mMenuController.getEstimatedMenuSize();
-        if (estimatedMenuSize == null) {
+        final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+        if (estimatedMinMenuSize == null) {
             Log.wtf(TAG, "Failed to get estimated menu size");
             return false;
         }
         final Rect currentBounds = mPipBoundsState.getBounds();
-        return currentBounds.width() < estimatedMenuSize.getWidth()
-                || currentBounds.height() < estimatedMenuSize.getHeight();
+        return currentBounds.width() < estimatedMinMenuSize.getWidth()
+                || currentBounds.height() < estimatedMinMenuSize.getHeight();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ccfdce6..24b0f30 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,10 +18,11 @@
 
 import android.graphics.Region
 import android.view.Surface
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
 import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.DOCKED_STACK_DIVIDER
 
 @JvmOverloads
 fun LayersAssertionBuilder.appPairsDividerIsVisible(
@@ -29,7 +30,7 @@
     enabled: Boolean = bugId == 0
 ) {
     end("appPairsDividerIsVisible", bugId, enabled) {
-        this.isVisible(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        this.isVisible(APP_PAIR_SPLIT_DIVIDER)
     }
 }
 
@@ -39,7 +40,7 @@
     enabled: Boolean = bugId == 0
 ) {
     end("appPairsDividerIsInVisible", bugId, enabled) {
-        this.notExists(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        this.notExists(APP_PAIR_SPLIT_DIVIDER)
     }
 }
 
@@ -107,7 +108,7 @@
     enabled: Boolean = bugId == 0
 ) {
     end("PrimaryAppBounds", bugId, enabled) {
-        val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
         this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation))
     }
 }
@@ -120,7 +121,7 @@
     enabled: Boolean = bugId == 0
 ) {
     end("SecondaryAppBounds", bugId, enabled) {
-        val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
         this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation))
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 3953c1c..89bbdb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -135,12 +135,4 @@
             throw RuntimeException(e)
         }
     }
-
-    companion object {
-        const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-        const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
-        const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
-        const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
-        const val IMAGE_WALLPAPER = "ImageWallpaper"
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 5cbfec6..257350b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
 import com.android.wm.shell.flicker.appPairsDividerIsVisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index f57a000..0b001f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
 import com.android.wm.shell.flicker.appPairsDividerIsInvisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
deleted file mode 100644
index dea5c30..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import org.junit.Assert
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test SplitScreen launch.
- * To run this test: `atest WMShellFlickerTests:EnterLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testLaunchActivity = "launch_splitScreen_test_activity"
-            withTestName {
-                testLaunchActivity
-            }
-            setup {
-                eachRun {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                    nonResizeableApp.exit()
-                }
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-            }
-        }
-
-    @Test
-    fun testEnterSplitScreen_dockActivity() {
-        val testTag = "testEnterSplitScreen_dockActivity"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackDividerBecomesVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LIVE_WALLPAPER_PACKAGE_NAME)
-                    )
-                }
-                windowManagerTrace {
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testEnterSplitScreen_launchToSide() {
-        val testTag = "testEnterSplitScreen_launchToSide"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackPrimaryBoundsIsVisible(
-                        rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                        rotation, secondaryApp.defaultWindowName, 169271943)
-                    dockedStackDividerBecomesVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                            secondaryApp.defaultWindowName)
-                    )
-                }
-                windowManagerTrace {
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                            .isVisible(secondaryApp.defaultWindowName)
-                    }
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                            secondaryApp.defaultWindowName))
-                }
-            }
-        }
-    }
-
-    @FlakyTest(bugId = 173875043)
-    @Test
-    fun testNonResizeableNotDocked() {
-        val testTag = "testNonResizeableNotDocked"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                nonResizeableApp.launchViaIntent()
-                uiDevice.openQuickstep()
-                if (uiDevice.canSplitScreen()) {
-                    Assert.fail("Non-resizeable app should not enter split screen")
-                }
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
-                    )
-                }
-                windowManagerTrace {
-                    end("appWindowIsVisible") {
-                        isInvisible(nonResizeableApp.defaultWindowName)
-                    }
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName))
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
new file mode 100644
index 0000000..5374bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity and dock to primary split screen
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenDockActivity(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenDockActivity", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 169271943)
+                        dockedStackDividerBecomesVisible()
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178531736
+                        )
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178531736
+                        )
+                        end("appWindowIsVisible") {
+                            isVisible(splitScreenApp.defaultWindowName)
+                        }
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
new file mode 100644
index 0000000..d750403
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity to primary split screen and dock secondary activity to side
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenLaunchToSide(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenLaunchToSide", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 169271943)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 169271943)
+                        dockedStackDividerBecomesVisible()
+                        // TODO(b/178447631) Remove Splash Screen from white list when flicker lib
+                        //                   add a wait for splash screen be gone
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+                                splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+                                splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName)
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
new file mode 100644
index 0000000..e361923
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.canSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickstep
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open non-resizable activity will auto exit split screen mode
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 173875043)
+class EnterSplitScreenNonResizableNotDock(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenNonResizeableActivityNotDock", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    device.openQuickstep()
+                    if (device.canSplitScreen()) {
+                        Assert.fail("Non-resizeable app should not enter split screen")
+                    }
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsInvisible()
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                SPLASH_SCREEN_NAME,
+                                nonResizeableApp.defaultWindowName,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(WALLPAPER_TITLE,
+                                LAUNCHER_PACKAGE_NAME,
+                                SPLASH_SCREEN_NAME,
+                                nonResizeableApp.defaultWindowName,
+                                splitScreenApp.defaultWindowName)
+                        )
+                        end("appWindowIsVisible") {
+                            isInvisible(nonResizeableApp.defaultWindowName)
+                        }
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
new file mode 100644
index 0000000..6aed83f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
+ * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitLegacySplitScreenFromBottom(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testExitLegacySplitScreenFromBottom", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    device.exitSplitScreenFromBottom()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesInvisible(DOCKED_STACK_DIVIDER)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesInVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName)
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
deleted file mode 100644
index a3b8673..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottomTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenFromBottomTest(
-    testSpec: FlickerTestRunnerFactory.TestSpec
-) : FlickerTestRunner(testSpec) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
-            // TODO(b/162923992) Use of multiple segments of flicker spec for testing
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
-                configuration ->
-                        withTestName {
-                            buildTestTag("exitSplitScreenFromBottom", configuration)
-                        }
-                        repeat { configuration.repetitions }
-                        setup {
-                            eachRun {
-                                device.wakeUpAndGoToHomeScreen()
-                                device.openQuickStepAndClearRecentAppsFromOverview()
-                                splitScreenApp.launchViaIntent(wmHelper)
-                                device.launchSplitScreen()
-                                device.waitForIdle()
-                                this.setRotation(configuration.endRotation)
-                            }
-                        }
-                        teardown {
-                            eachRun {
-                                if (device.isInSplitScreen()) {
-                                    device.exitSplitScreen()
-                                }
-                                splitScreenApp.exit()
-                            }
-                        }
-                        transitions {
-                            device.exitSplitScreenFromBottom()
-                        }
-                        assertions {
-                            windowManagerTrace {
-                                all("isNotEmpty") { isNotEmpty() }
-                            }
-                        }
-                    }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
deleted file mode 100644
index 701b0d0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test exit SplitScreen mode.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testLaunchActivity = "launch_splitScreen_test_activity"
-            withTestName {
-                testLaunchActivity
-            }
-            setup {
-                eachRun {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                    secondaryApp.launchViaIntent()
-                    splitScreenApp.launchViaIntent()
-                    uiDevice.launchSplitScreen()
-                }
-            }
-            teardown {
-                eachRun {
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    visibleWindowsShownMoreThanOneConsecutiveEntry()
-                }
-                layersTrace {
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME))
-                }
-            }
-        }
-
-    @Test
-    fun testEnterSplitScreen_exitPrimarySplitScreenMode() {
-        val testTag = "testEnterSplitScreen_exitPrimarySplitScreenMode"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                uiDevice.exitSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    layerBecomesInvisible(splitScreenApp.defaultWindowName)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsInvisible") {
-                        isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    @FlakyTest(bugId = 172811376)
-    fun testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen() {
-        val testTag = "testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.reopenAppFromOverview()
-                uiDevice.resizeSplitScreen(startRatio)
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        private val startRatio = Rational(1, 3)
-
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
new file mode 100644
index 0000000..59f6aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen, and open secondary to side, exit primary split
+ * and test secondary activity become full screen.
+ * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPrimarySplitScreenShowSecondaryFullscreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testExitPrimarySplitScreenShowSecondaryFullscreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                    // TODO(b/175687842) Can not find Split screen divider, use exit() instead
+                    splitScreenApp.exit()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsInvisible(bugId = 175687842)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
similarity index 60%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 4dcbdff..03b6edf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -52,13 +52,13 @@
 
 /**
  * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncherTest`
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
  */
 @Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LegacySplitScreenToLauncherTest(
+class LegacySplitScreenToLauncher(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
     companion object {
@@ -67,68 +67,68 @@
         fun getParams(): Collection<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
-                    .launcherStrategy.supportedLauncherPackage
+                .launcherStrategy.supportedLauncherPackage
             val testApp = SimpleAppHelper(instrumentation)
 
             // b/161435597 causes the test not to work on 90 degrees
             return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
-                    withTestName {
-                        buildTestTag("splitScreenToLauncher", configuration)
+                withTestName {
+                    buildTestTag("splitScreenToLauncher", configuration)
+                }
+                repeat { configuration.repetitions }
+                setup {
+                    test {
+                        device.wakeUpAndGoToHomeScreen()
+                        device.openQuickStepAndClearRecentAppsFromOverview()
                     }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                            device.openQuickStepAndClearRecentAppsFromOverview()
-                        }
-                        eachRun {
-                            testApp.launchViaIntent(wmHelper)
-                            this.setRotation(configuration.endRotation)
-                            device.launchSplitScreen()
-                            device.waitForIdle()
-                        }
+                    eachRun {
+                        testApp.launchViaIntent(wmHelper)
+                        this.setRotation(configuration.endRotation)
+                        device.launchSplitScreen()
+                        device.waitForIdle()
                     }
-                    teardown {
-                        eachRun {
-                            testApp.exit()
-                        }
-                        test {
-                            if (device.isInSplitScreen()) {
-                                device.exitSplitScreen()
-                            }
-                        }
+                }
+                teardown {
+                    eachRun {
+                        testApp.exit()
                     }
-                    transitions {
-                        device.exitSplitScreen()
-                    }
-                    assertions {
-                        windowManagerTrace {
-                            navBarWindowIsAlwaysVisible()
-                            statusBarWindowIsAlwaysVisible()
-                            visibleWindowsShownMoreThanOneConsecutiveEntry()
-                        }
-
-                        layersTrace {
-                            navBarLayerIsAlwaysVisible()
-                            statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.endRotation)
-                            navBarLayerRotatesAndScales(configuration.endRotation)
-                            statusBarLayerRotatesScales(configuration.endRotation)
-                            visibleLayersShownMoreThanOneConsecutiveEntry(
-                                    listOf(launcherPackageName))
-
-                            // b/161435597 causes the test not to work on 90 degrees
-                            dockedStackDividerBecomesInvisible()
-
-                            layerBecomesInvisible(testApp.getPackage())
-                        }
-
-                        eventLog {
-                            focusDoesNotChange(bugId = 151179149)
+                    test {
+                        if (device.isInSplitScreen()) {
+                            device.exitSplitScreen()
                         }
                     }
                 }
+                transitions {
+                    device.exitSplitScreen()
+                }
+                assertions {
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry()
+                    }
+
+                    layersTrace {
+                        navBarLayerIsAlwaysVisible()
+                        statusBarLayerIsAlwaysVisible()
+                        noUncoveredRegions(configuration.endRotation)
+                        navBarLayerRotatesAndScales(configuration.endRotation)
+                        statusBarLayerRotatesScales(configuration.endRotation)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(launcherPackageName))
+
+                        // b/161435597 causes the test not to work on 90 degrees
+                        dockedStackDividerBecomesInvisible()
+
+                        layerBecomesInvisible(testApp.getPackage())
+                    }
+
+                    eventLog {
+                        focusDoesNotChange(bugId = 151179149)
+                    }
+                }
+            }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
new file mode 100644
index 0000000..328ff88
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE2.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.wm.shell.flicker.legacysplitscreen
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Surface
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.startRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class LegacySplitScreenTransition(
+    protected val instrumentation: Instrumentation
+) {
+    internal val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
+    internal val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+    internal val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
+    internal val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
+        .launcherStrategy.supportedLauncherPackage
+    internal val LIVE_WALLPAPER_PACKAGE_NAME =
+        "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+    internal val LETTERBOX_NAME = "Letterbox"
+    internal val TOAST_NAME = "Toast"
+    internal val SPLASH_SCREEN_NAME = "Splash Screen"
+
+    internal open val defaultTransitionSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    secondaryApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    this.setRotation(configuration.startRotation)
+                }
+            }
+            teardown {
+                eachRun {
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+
+    internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    this.setRotation(configuration.startRotation)
+                }
+            }
+            teardown {
+                eachRun {
+                    nonResizeableApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+
+    internal open val customRotateSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    secondaryApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                }
+            }
+            teardown {
+                eachRun {
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
new file mode 100644
index 0000000..42c7b7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via recent overview)
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testNonResizableDismissInLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    device.launchSplitScreen()
+                    nonResizeableApp.reopenAppFromOverview()
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesVisible(nonResizeableApp.defaultWindowName)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+                                LETTERBOX_NAME, TOAST_NAME,
+                                splitScreenApp.defaultWindowName,
+                                nonResizeableApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+                                LETTERBOX_NAME, TOAST_NAME,
+                                splitScreenApp.defaultWindowName,
+                                nonResizeableApp.defaultWindowName)
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, cleanSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
deleted file mode 100644
index 6fca580..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
-    @Test
-    fun testNonResizableDismissInLegacySplitScreenTest() {
-        val testTag = "testNonResizableDismissInLegacySplitScreenTest"
-
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            transitions {
-                nonResizeableApp.launchViaIntent(wmHelper)
-                splitScreenApp.launchViaIntent(wmHelper)
-                device.launchSplitScreen()
-                nonResizeableApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
-                                    TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
-                            bugId = 178447631
-                    )
-                }
-                windowManagerTrace {
-                    end("nonResizeableAppWindowIsVisible") {
-                        isVisible(nonResizeableApp.defaultWindowName)
-                            .isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
new file mode 100644
index 0000000..5b8ec1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via intent)
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testNonResizableLaunchInLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    device.launchSplitScreen()
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesVisible(nonResizeableApp.defaultWindowName)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                                listOf(DOCKED_STACK_DIVIDER,
+                                        LAUNCHER_PACKAGE_NAME,
+                                        LETTERBOX_NAME,
+                                        nonResizeableApp.defaultWindowName,
+                                        splitScreenApp.defaultWindowName)
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                                listOf(DOCKED_STACK_DIVIDER,
+                                        LAUNCHER_PACKAGE_NAME,
+                                        LETTERBOX_NAME,
+                                        nonResizeableApp.defaultWindowName,
+                                        splitScreenApp.defaultWindowName)
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, cleanSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
deleted file mode 100644
index deae41f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
-    @Test
-    fun testNonResizableLaunchInLegacySplitScreenTest() {
-        val testTag = "testNonResizableLaunchInLegacySplitScreenTest"
-
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            transitions {
-                nonResizeableApp.launchViaIntent(wmHelper)
-                splitScreenApp.launchViaIntent(wmHelper)
-                device.launchSplitScreen()
-                nonResizeableApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
-                                    TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
-                        bugId = 178447631
-                    )
-                }
-                windowManagerTrace {
-                    end("nonResizeableAppWindowIsVisible") {
-                        isVisible(nonResizeableApp.defaultWindowName)
-                            .isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
new file mode 100644
index 0000000..c802ffe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val wmHelper = WindowManagerStateHelper()
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testOpenAppToLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    windowManagerTrace {
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+                            bugId = 178447631)
+                        appWindowBecomesVisible(splitScreenApp.getPackage())
+                    }
+
+                    layersTrace {
+                        noUncoveredRegions(configuration.startRotation, enabled = false)
+                        statusBarLayerIsAlwaysVisible()
+                        appPairsDividerBecomesVisible()
+                        layerBecomesVisible(splitScreenApp.getPackage())
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+                            bugId = 178447631)
+                    }
+
+                    eventLog {
+                        focusChanges(splitScreenApp.`package`,
+                            "recents_animation_input_consumer", "NexusLauncherActivity",
+                            bugId = 151179149)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
deleted file mode 100644
index 6200a69..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppToLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    @Test
-    fun OpenAppToLegacySplitScreenTest() {
-        val testTag = "OpenAppToLegacySplitScreenTest"
-        val helper = WindowManagerStateHelper()
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            setup {
-                eachRun {
-                    splitScreenApp.launchViaIntent(wmHelper)
-                    device.pressHome()
-                    this.setRotation(rotation)
-                }
-            }
-            transitions {
-                device.launchSplitScreen()
-                helper.waitForAppTransitionIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LETTER_BOX_NAME)
-                    )
-                    appWindowBecomesVisible(splitScreenApp.getPackage())
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation, enabled = false)
-                    statusBarLayerIsAlwaysVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LETTER_BOX_NAME))
-                    appPairsDividerBecomesVisible()
-                    layerBecomesVisible(splitScreenApp.getPackage())
-                }
-
-                eventLog {
-                    focusChanges(splitScreenApp.`package`,
-                            "recents_animation_input_consumer", "NexusLauncherActivity",
-                            bugId = 151179149)
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            // TODO(b/161435597) causes the test not to work on 90 degrees
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 95c1c16..54a37d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -58,7 +58,7 @@
 
 /**
  * Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreenTest`
+ * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
  *
  * Currently it runs only in 0 degrees because of b/156100803
  */
@@ -67,7 +67,7 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 159096424)
-class ResizeLegacySplitScreenTest(
+class ResizeLegacySplitScreen(
     testSpec: FlickerTestRunnerFactory.TestSpec
 ) : FlickerTestRunner(testSpec) {
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..214269e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen and rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppAndEnterSplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateOneLaunchedAppAndEnterSplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    this.setRotation(configuration.startRotation)
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..4290c92
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppInSplitScreenMode(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateOneLaunchedAppInSplitScreenMode", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                    device.launchSplitScreen()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
deleted file mode 100644
index 07571c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateOneLaunchedAppTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenRotationSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testSetupRotation = "testSetupRotation"
-            withTestName {
-                testSetupRotation
-            }
-            setup {
-                test {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    setRotation(Surface.ROTATION_0)
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-        }
-
-    @Test
-    fun testRotateInSplitScreenMode() {
-        val testTag = "testEnterSplitScreen_launchToSide"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                setRotation(rotation)
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testRotateAndEnterSplitScreenMode() {
-        val testTag = "testRotateAndEnterSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                setRotation(rotation)
-                uiDevice.launchSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..4095b9a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppAndEnterSplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateTwoLaunchedAppAndEnterSplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, 175687842)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..aebf606
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppInSplitScreenMode(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateTwoLaunchedAppInSplitScreenMode", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                setup {
+                    eachRun {
+                        device.launchSplitScreen()
+                        splitScreenApp.reopenAppFromOverview()
+                        this.setRotation(configuration.startRotation)
+                    }
+                }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
deleted file mode 100644
index d8014d3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateTwoLaunchedAppTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenRotationSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testSetupRotation = "testSetupRotation"
-            withTestName {
-                testSetupRotation
-            }
-            setup {
-                test {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    setRotation(Surface.ROTATION_0)
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-        }
-
-    @Test
-    fun testRotateInSplitScreenMode() {
-        val testTag = "testRotateInSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-                setRotation(rotation)
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                            rotation, secondaryApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                            .isVisible(secondaryApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @FlakyTest(bugId = 173875043)
-    @Test
-    fun testRotateAndEnterSplitScreenMode() {
-        val testTag = "testRotateAndEnterSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                setRotation(rotation)
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                            rotation, secondaryApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end("appWindowIsVisible") {
-                        isVisible(splitScreenApp.defaultWindowName)
-                                .isVisible(secondaryApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
deleted file mode 100644
index 01db4ed6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.NonRotationTestBase
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-
-abstract class SplitScreenTestBase(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
-    protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
-    protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
-    protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
-            .launcherStrategy.supportedLauncherPackage
-    protected val LIVE_WALLPAPER_PACKAGE_NAME =
-            "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
-    protected val LETTER_BOX_NAME = "Letterbox"
-    protected val TOAST_NAME = "Toast"
-
-    protected val transitionSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-                setup {
-                    eachRun {
-                        uiDevice.wakeUpAndGoToHomeScreen()
-                        uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                    }
-                }
-                teardown {
-                    eachRun {
-                        if (uiDevice.isInSplitScreen()) {
-                            uiDevice.exitSplitScreen()
-                        }
-                        splitScreenApp.exit()
-                        nonResizeableApp.exit()
-                    }
-                }
-                assertions {
-                    layersTrace {
-                        navBarLayerIsAlwaysVisible()
-                        statusBarLayerIsAlwaysVisible()
-                    }
-                    windowManagerTrace {
-                        navBarWindowIsAlwaysVisible()
-                        statusBarWindowIsAlwaysVisible()
-                    }
-                }
-            }
-}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 615bf4d..ce1d96c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -435,6 +435,7 @@
         "canvas/CanvasFrontend.cpp",
         "canvas/CanvasOpBuffer.cpp",
         "canvas/CanvasOpRasterizer.cpp",
+        "effects/StretchEffect.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 96118aa..64b8b71 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -561,7 +561,7 @@
                 return;
             }
             c->concat(invertedMatrix);
-            mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+            mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop);
         } else {
             c->drawDrawable(drawable.get());
         }
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index aeb60e6..609706e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -23,6 +23,7 @@
 #include "Outline.h"
 #include "Rect.h"
 #include "RevealClip.h"
+#include "effects/StretchEffect.h"
 #include "utils/MathUtils.h"
 #include "utils/PaintUtils.h"
 
@@ -98,6 +99,10 @@
 
     SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
 
+    const StretchEffect& getStretchEffect() const { return mStretchEffect; }
+
+    StretchEffect& mutableStretchEffect() { return mStretchEffect; }
+
     // Sets alpha, xfermode, and colorfilter from an SkPaint
     // paint may be NULL, in which case defaults will be set
     bool setFromPaint(const SkPaint* paint);
@@ -124,6 +129,7 @@
     SkBlendMode mMode;
     sk_sp<SkColorFilter> mColorFilter;
     sk_sp<SkImageFilter> mImageFilter;
+    StretchEffect mStretchEffect;
 };
 
 /*
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
new file mode 100644
index 0000000..51cbc75
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StretchEffect.h"
+
+namespace android::uirenderer {
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
+    // TODO: Implement & Cache
+    // Probably need to use mutable to achieve caching
+    return nullptr;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
new file mode 100644
index 0000000..7dfd639
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.h
@@ -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.
+ */
+
+#pragma once
+
+#include "utils/MathUtils.h"
+
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkImageFilter.h>
+
+namespace android::uirenderer {
+
+// TODO: Inherit from base RenderEffect type?
+class StretchEffect {
+public:
+    enum class StretchInterpolator {
+        SmoothStep,
+    };
+
+    bool isEmpty() const {
+        return MathUtils::isZero(stretchDirection.x())
+                && MathUtils::isZero(stretchDirection.y());
+    }
+
+    void setEmpty() {
+        *this = StretchEffect{};
+    }
+
+    void mergeWith(const StretchEffect& other) {
+        if (other.isEmpty()) {
+            return;
+        }
+        if (isEmpty()) {
+            *this = other;
+            return;
+        }
+        stretchDirection += other.stretchDirection;
+        if (isEmpty()) {
+            return setEmpty();
+        }
+        stretchArea.join(other.stretchArea);
+        maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+    }
+
+    sk_sp<SkImageFilter> getImageFilter() const;
+
+    SkRect stretchArea {0, 0, 0, 0};
+    SkVector stretchDirection {0, 0};
+    float maxStretchAmount = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8b35d96..8023968 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -166,6 +166,31 @@
     return true;
 }
 
+static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    auto& stretch = renderNode->mutateStagingProperties()
+            .mutateLayerProperties().mutableStretchEffect();
+    if (stretch.isEmpty()) {
+        return false;
+    }
+    stretch.setEmpty();
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
+static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+        jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
+            StretchEffect{
+        .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
+        .stretchDirection = {.fX = vX, .fY = vY},
+        .maxStretchAmount = max
+    });
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
 static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     return renderNode->stagingProperties().hasShadow();
@@ -678,6 +703,8 @@
     { "nSetOutlinePath",       "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
     { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
     { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
+    { "nClearStretch",         "(J)Z",   (void*) android_view_RenderNode_clearStretch },
+    { "nStretch",              "(JFFFFFFF)Z",   (void*) android_view_RenderNode_stretch },
     { "nHasShadow",            "(J)Z",   (void*) android_view_RenderNode_hasShadow },
     { "nSetSpotShadowColor",   "(JI)Z",  (void*) android_view_RenderNode_setSpotShadowColor },
     { "nGetSpotShadowColor",   "(J)I",   (void*) android_view_RenderNode_getSpotShadowColor },
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index c6c9e9d..71f533c 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -194,7 +194,7 @@
         canvas->concat(invertedMatrix);
 
         const SkIRect deviceBounds = canvas->getDeviceClipBounds();
-        tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+        tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop);
     }
 }
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 75815bb6..c010212 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -20,6 +20,8 @@
 #include "SkiaDisplayList.h"
 #include "utils/TraceUtils.h"
 
+#include <include/effects/SkImageFilters.h>
+
 #include <optional>
 
 namespace android {
@@ -171,11 +173,25 @@
                             SkPaint* paint) {
     if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
         properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
-        properties.getImageFilter() != nullptr) {
+        properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
         paint->setAlpha(properties.alpha() * alphaMultiplier);
         paint->setBlendMode(properties.xferMode());
         paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
-        paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
+
+        sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
+        sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+        sk_sp<SkImageFilter> filter;
+        if (imageFilter && stretchFilter) {
+            filter = SkImageFilters::Compose(
+                  std::move(stretchFilter),
+                  std::move(imageFilter)
+            );
+        } else if (stretchFilter) {
+            filter = std::move(stretchFilter);
+        } else {
+            filter = std::move(imageFilter);
+        }
+        paint->setImageFilter(std::move(filter));
         return true;
     }
     return false;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eacabfd..633f21c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -490,9 +490,11 @@
 
     if (mNativeSurface) {
         // TODO(b/165985262): measure performance impact
-        if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
-                vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
-            native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+        const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+        if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+            const auto inputEventId = mCurrentFrameInfo->get(FrameInfoIndex::NewestInputEvent);
+            native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
+                                                  inputEventId);
         }
     }
 
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 3d188c0..65a0110 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -31,6 +31,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -1775,6 +1776,7 @@
      */
     @Nullable
     @SystemApi
+    @SuppressLint("NullableCollection")
     public Collection<CorrelationVector> getCorrelationVectors() {
         return mReadOnlyCorrelationVectors;
     }
@@ -1785,7 +1787,9 @@
      * @hide
      */
     @TestApi
-    public void setCorrelationVectors(@Nullable Collection<CorrelationVector> correlationVectors) {
+    public void setCorrelationVectors(
+            @SuppressLint("NullableCollection")
+            @Nullable Collection<CorrelationVector> correlationVectors) {
         if (correlationVectors == null || correlationVectors.isEmpty()) {
             resetCorrelationVectors();
         } else {
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 96ec590..fdb044d 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2233,6 +2233,7 @@
      * @see #getGnssCapabilities()
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<GnssAntennaInfo> getGnssAntennaInfos() {
         try {
             return mService.getGnssAntennaInfos();
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 1306ea2..8f455cd 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -255,7 +255,9 @@
     /**
      * Implements optional custom commands.
      */
-    public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras);
+    public abstract void onSendExtraCommand(@NonNull String command,
+            @SuppressLint("NullableCollection")
+            @Nullable Bundle extras);
 
     private final class Service extends ILocationProvider.Stub {
 
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bcc846b..bb1dbd4 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -739,6 +739,13 @@
             if (mBundle != null) {
                 aa.mBundle = new Bundle(mBundle);
             }
+
+            // Allow the FLAG_HW_HOTWORD only for AudioSource.VOICE_RECOGNITION
+            if (mSource != MediaRecorder.AudioSource.VOICE_RECOGNITION
+                    && (mFlags & FLAG_HW_HOTWORD) == FLAG_HW_HOTWORD) {
+                aa.mFlags &= ~FLAG_HW_HOTWORD;
+            }
+
             return aa;
         }
 
@@ -852,6 +859,23 @@
         }
 
         /**
+         * @hide
+         * Request for capture in hotword mode.
+         *
+         * Requests an audio path optimized for Hotword detection use cases from
+         * the low power audio DSP. This is valid only for capture with
+         * audio source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION}.
+         * There is no guarantee that this mode is available on the device.
+         * @return the same Builder instance.
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+        public @NonNull Builder setHotwordMode() {
+            mFlags |= FLAG_HW_HOTWORD;
+            return this;
+        }
+
+        /**
          * Specifies whether the audio may or may not be captured by other apps or the system.
          *
          * The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionManager.java b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
index 6bbcfd3..b183eaf 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionManager.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -90,7 +91,9 @@
          *               supplied bundle
          */
         void onRecognitionSucceeded(@NonNull RecognitionRequest recognitionRequest,
-                @NonNull MediaMetadata result, @Nullable Bundle extras);
+                @NonNull MediaMetadata result,
+                @SuppressLint("NullableCollection")
+                @Nullable Bundle extras);
 
         /**
          * Invoked when the search is not successful (possibly but not necessarily due to error).
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
index e2071b8..04b4c39b 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionService.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
@@ -53,7 +54,9 @@
          * @param extras extra data to be supplied back to the caller. Note that all executable
          *               parameters and file descriptors would be removed from the supplied bundle
          */
-        void onRecognitionSucceeded(@NonNull MediaMetadata result, @Nullable Bundle extras);
+        void onRecognitionSucceeded(@NonNull MediaMetadata result,
+                @SuppressLint("NullableCollection")
+                @Nullable Bundle extras);
 
         /**
          * Call this method if the search does not find a result on an error occurred.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4d835b2..8525e99 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.hardware.tv.tuner.V1_0.Constants;
@@ -1017,6 +1018,7 @@
      * failed.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<FrontendInfo> getAvailableFrontendInfos() {
         FrontendInfo[] feInfoList = getFrontendInfoListInternal();
         if (feInfoList == null) {
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index c064de2..08a8d89 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,7 +18,6 @@
 
 #include <binder/Parcel.h>
 #include <jni.h>
-#include <media/IMediaMetricsService.h>
 #include <media/MediaMetricsItem.h>
 #include <nativehelper/JNIHelp.h>
 #include <variant>
@@ -151,12 +150,7 @@
         return (jint)BAD_VALUE;
     }
 
-    sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
-    if (service == nullptr) {
-        ALOGW("Cannot retrieve mediametrics service");
-        return (jint)NO_INIT;
-    }
-    return (jint)service->submitBuffer((char *)buffer, length);
+    return (jint)mediametrics::BaseItem::submitBuffer((char *)buffer, length);
 }
 
 // Helper function to convert a native PersistableBundle to a Java
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index d464587..3d633ea 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -27,14 +27,13 @@
     ],
 
     shared_libs: [
-        "libandroid_runtime",
         "libhwui",
         "liblog",
     ],
 
     header_libs: [
-        "libhwui_internal_headers",
         "jni_headers",
+        "libhwui_internal_headers",
     ],
 
     static_libs: ["libarect"],
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
new file mode 100644
index 0000000..9afa5d1
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for all core connectivity services.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializer {
+    private ConnectivityFrameworkInitializer() {}
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all core
+     * connectivity services to {@link Context}, so that {@link Context#getSystemService} can
+     * return them.
+     *
+     * @throws IllegalStateException if this is called anywhere besides
+     * {@link SystemServiceRegistry}.
+     */
+    public static void registerServiceWrappers() {
+        // registerContextAwareService will throw if this is called outside of SystemServiceRegistry
+        // initialization.
+        SystemServiceRegistry.registerContextAwareService(
+                Context.CONNECTIVITY_SERVICE,
+                ConnectivityManager.class,
+                (context, serviceBinder) -> {
+                    IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
+                    return new ConnectivityManager(context, icm);
+                }
+        );
+
+        // TODO: move outside of the connectivity JAR
+        SystemServiceRegistry.registerContextAwareService(
+                Context.VPN_MANAGEMENT_SERVICE,
+                VpnManager.class,
+                (context) -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.createVpnManager();
+                }
+        );
+
+        SystemServiceRegistry.registerContextAwareService(
+                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                ConnectivityDiagnosticsManager.class,
+                (context) -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.createDiagnosticsManager();
+                }
+        );
+
+        SystemServiceRegistry.registerContextAwareService(
+                Context.TEST_NETWORK_SERVICE,
+                TestNetworkManager.class,
+                context -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.startOrGetTestNetworkManager();
+                }
+        );
+    }
+}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index ac8f9c9..2a0a74e 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -4823,6 +4823,28 @@
         }
     }
 
+    /** @hide */
+    public TestNetworkManager startOrGetTestNetworkManager() {
+        final IBinder tnBinder;
+        try {
+            tnBinder = mService.startOrGetTestNetworkService();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
+    }
+
+    /** @hide */
+    public VpnManager createVpnManager() {
+        return new VpnManager(mContext, mService);
+    }
+
+    /** @hide */
+    public ConnectivityDiagnosticsManager createDiagnosticsManager() {
+        return new ConnectivityDiagnosticsManager(mContext, mService);
+    }
+
     /**
      * Simulates a Data Stall for the specified Network.
      *
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 3843b9a..8bfa77a 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -34,9 +34,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.CollectionUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -401,11 +401,18 @@
     public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
 
     /**
-     * Indicates that this network is not managed by a Virtual Carrier Network (VCN).
-     *
-     * TODO(b/177299683): Add additional clarifying javadoc.
+     * Indicates that this network is not subsumed by a Virtual Carrier Network (VCN).
+     * <p>
+     * To provide an experience on a VCN similar to a single traditional carrier network, in
+     * some cases the system sets this bit is set by default in application's network requests,
+     * and may choose to remove it at its own discretion when matching the request to a network.
+     * <p>
+     * Applications that want to know about a Virtual Carrier Network's underlying networks,
+     * for example to use them for multipath purposes, should remove this bit from their network
+     * requests ; the system will not add it back once removed.
      * @hide
      */
+    @SystemApi
     public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
 
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
@@ -767,7 +774,7 @@
         if (originalOwnerUid == creatorUid) {
             setOwnerUid(creatorUid);
         }
-        if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) {
+        if (CollectionUtils.contains(originalAdministratorUids, creatorUid)) {
             setAdministratorUids(new int[] {creatorUid});
         }
         // There is no need to clear the UIDs, they have already been cleared by clearAll() above.
@@ -1873,7 +1880,7 @@
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
 
-        if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+        if (mAdministratorUids != null && mAdministratorUids.length != 0) {
             sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
         }
 
@@ -2506,7 +2513,7 @@
         @NonNull
         public NetworkCapabilities build() {
             if (mCaps.getOwnerUid() != Process.INVALID_UID) {
-                if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
+                if (!CollectionUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
                     throw new IllegalStateException("The owner UID must be included in "
                             + " administrator UIDs.");
                 }
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index c8f3bd3..8fc3181 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -57,6 +57,7 @@
     static_libs: [
         "net-utils-device-common",
         "net-utils-framework-common",
+        "netd-client",
     ],
     apex_available: [
         "//apex_available:platform",
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
index 8a73fb5..2be00b9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
@@ -15,11 +15,17 @@
   limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+      android:top="4dp"
+      android:left="4dp"
+      android:right="4dp"
+      android:bottom="4dp">
 
-  <size android:height="24dp" android:width="24dp" />
-  <solid android:color="@color/thumb_off" />
-  <stroke android:width="4dp" android:color="@android:color/transparent" />
+    <shape android:shape="oval" >
+      <size android:height="20dp" android:width="20dp" />
+      <solid android:color="@color/thumb_off" />
+    </shape>
 
-</shape>
+  </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
index 8a0af00..e85eb42 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
@@ -15,11 +15,17 @@
   limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+      android:top="4dp"
+      android:left="4dp"
+      android:right="4dp"
+      android:bottom="4dp">
 
-  <size android:height="24dp" android:width="24dp" />
-  <solid android:color="?android:attr/colorAccent" />
-  <stroke android:width="4dp" android:color="@android:color/transparent" />
+    <shape android:shape="oval" >
+      <size android:height="20dp" android:width="20dp" />
+      <solid android:color="?android:attr/colorAccent" />
+    </shape>
 
-</shape>
+  </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
index 1be3a8e..b29f459 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
@@ -20,7 +20,7 @@
   <item
       android:width="13.33dp"
       android:height="1.67dp"
-      android:left="19dp"
+      android:left="21dp"
       android:gravity="center"
       android:drawable="@drawable/track_off_indicator" />
 </layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
index 3cc490f..c838654 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
@@ -17,17 +17,17 @@
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="24dp"
-    android:viewportWidth="48"
-    android:viewportHeight="24">
+    android:width="52dp"
+    android:height="28dp"
+    android:viewportWidth="52"
+    android:viewportHeight="28">
 
   <group>
     <clip-path
-        android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+        android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
 
     <path
-        android:pathData="M0 0V24H48V0"
+        android:pathData="M0 0V28H52V0"
         android:fillColor="@color/track_off" />
   </group>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
index 2553891..cf24112 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
@@ -21,6 +21,6 @@
       android:width="13.19dp"
       android:height="10.06dp"
       android:gravity="center"
-      android:right="19dp"
+      android:right="21dp"
       android:drawable="@drawable/track_on_indicator" />
 </layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
index 68ce19b..bb1a7ef 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
@@ -17,19 +17,20 @@
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="24dp"
-    android:viewportWidth="48"
-    android:viewportHeight="24"
+    android:width="52dp"
+    android:height="28dp"
+    android:viewportWidth="52"
+    android:viewportHeight="28"
     android:tint="@*android:color/switch_track_material">
 
   <group>
     <clip-path
-        android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+        android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
 
     <path
-        android:pathData="M0 0V24H48V0"
+        android:pathData="M0 0V28H52V0"
         android:fillColor="@*android:color/white_disabled_material" />
+
   </group>
 
 </vector>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
index 7e3ce9d..52779bc 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
@@ -26,7 +26,7 @@
         android:minHeight="@dimen/min_switch_bar_height"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
-        android:background="?android:attr/colorBackground"
+        android:background="?android:attr/selectableItemBackground"
         android:paddingLeft="@dimen/switchbar_margin_start"
         android:paddingRight="@dimen/switchbar_margin_end">
 
@@ -35,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_width="0dp"
             android:layout_weight="1"
-            android:paddingRight="54dp"
+            android:layout_marginRight="16dp"
             android:layout_gravity="center_vertical"
             android:maxLines="2"
             android:ellipsize="end"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
index 9dc0af3..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
@@ -17,7 +17,6 @@
 
 <resources>
 
-    <color name="title_text_color">@*android:color/primary_text_dark</color>
     <color name="thumb_off">#BFFFFFFF</color>
     <color name="track_off">@*android:color/material_grey_600</color>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
index 8194bdd..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
@@ -17,7 +17,6 @@
 
 <resources>
 
-    <color name="title_text_color">@*android:color/primary_text_light</color>
     <color name="thumb_off">#BFFFFFFF</color>
     <color name="track_off">@*android:color/material_grey_600</color>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index dd443de..b145c9b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -18,13 +18,13 @@
 <resources>
 
     <!-- Size of layout margin left -->
-    <dimen name="switchbar_margin_start">26dp</dimen>
+    <dimen name="switchbar_margin_start">22dp</dimen>
 
     <!-- Size of layout margin right -->
-    <dimen name="switchbar_margin_end">24dp</dimen>
+    <dimen name="switchbar_margin_end">16dp</dimen>
 
     <!-- Minimum width of switch -->
-    <dimen name="min_switch_width">48dp</dimen>
+    <dimen name="min_switch_width">52dp</dimen>
 
     <!-- Minimum width of switch bar -->
     <dimen name="min_switch_bar_height">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index fbb896c..59b5899 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -19,7 +19,6 @@
 
     <style name="MainSwitchText">
         <item name="android:textSize">20sp</item>
-        <item name="android:textColor">@color/title_text_color</item>
     </style>
 
     <style name="Settings.MainSwitch" parent="@android:style/Widget.Material.CompoundButton.Switch">
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 532c996..74b6578 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -136,10 +136,8 @@
      * Show the MainSwitchBar
      */
     public void show() {
-        if (!isShowing()) {
-            setVisibility(View.VISIBLE);
-            mSwitch.setOnCheckedChangeListener(this);
-        }
+        setVisibility(View.VISIBLE);
+        mSwitch.setOnCheckedChangeListener(this);
     }
 
     /**
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index dae0e70..274bf8d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -17,9 +17,11 @@
 package com.android.settingslib.widget;
 
 import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.widget.Switch;
 
+import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.TwoStatePreference;
 
@@ -38,30 +40,29 @@
     private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
 
     private MainSwitchBar mMainSwitchBar;
-    private Switch mSwitch;
     private String mTitle;
 
     private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
 
     public MainSwitchPreference(Context context) {
         super(context);
-        init();
+        init(context, null);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
+        init(context, attrs);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
+        init(context, attrs);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
+        init(context, attrs);
     }
 
     @Override
@@ -76,8 +77,21 @@
         registerListenerToSwitchBar();
     }
 
-    private void init() {
+    private void init(Context context, AttributeSet attrs) {
         setLayoutResource(R.layout.main_switch_layout);
+
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs,
+                    androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
+                    0 /*defStyleRes*/);
+            final CharSequence title = TypedArrayUtils.getText(a,
+                    androidx.preference.R.styleable.Preference_title,
+                    androidx.preference.R.styleable.Preference_android_title);
+            if (!TextUtils.isEmpty(title)) {
+                setTitle(title.toString());
+            }
+            a.recycle();
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index f346a59..3331550 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -6,6 +6,7 @@
 
     static_libs: [
         "androidx.preference_preference",
+        "androidx-constraintlayout_constraintlayout",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index 9dbd5fa..f45105d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
@@ -26,29 +27,40 @@
     android:paddingTop="32dp"
     android:paddingBottom="32dp">
 
-    <RelativeLayout
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
         <TextView
             android:id="@+id/usage_summary"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:layout_alignBaseline="@id/total_summary"
-            android:singleLine="true"
+            app:layout_constraintWidth_percent="0.45"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBaseline_toBaselineOf="@id/total_summary"
             android:ellipsize="marquee"
             android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Display1"
-            android:textSize="20sp"/>
+            android:textSize="14sp"
+            android:textAlignment="viewStart"/>
         <TextView
             android:id="@+id/total_summary"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:singleLine="true"
+            app:layout_constraintWidth_percent="0.45"
+            app:layout_constraintEnd_toStartOf="@id/custom_content"
             android:ellipsize="marquee"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"/>
-    </RelativeLayout>
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
+            android:textSize="14sp"
+            android:textAlignment="viewEnd"/>
+        <FrameLayout
+            android:id="@+id/custom_content"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/total_summary"
+            app:layout_constraintWidth_percent="0.1"/>
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <ProgressBar
         android:id="@android:id/progress"
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 950a8b4..af64a1d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -22,6 +22,9 @@
 import android.text.TextUtils;
 import android.text.style.RelativeSizeSpan;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -41,6 +44,7 @@
 
     private CharSequence mUsageSummary;
     private CharSequence mTotalSummary;
+    private ImageView mCustomImageView;
     private int mPercent = -1;
 
     /**
@@ -110,6 +114,15 @@
         notifyChanged();
     }
 
+    /** Set custom ImageView to the right side of total summary. */
+    public <T extends ImageView> void setCustomContent(T imageView) {
+        if (imageView == mCustomImageView) {
+            return;
+        }
+        mCustomImageView = imageView;
+        notifyChanged();
+    }
+
     /**
      * Binds the created View to the data for this preference.
      *
@@ -141,6 +154,15 @@
             progressBar.setIndeterminate(false);
             progressBar.setProgress(mPercent);
         }
+
+        final FrameLayout customLayout = (FrameLayout) holder.findViewById(R.id.custom_content);
+        if (mCustomImageView == null) {
+            customLayout.removeAllViews();
+            customLayout.setVisibility(View.GONE);
+        } else {
+            customLayout.addView(mCustomImageView);
+            customLayout.setVisibility(View.VISIBLE);
+        }
     }
 
     private CharSequence enlargeFontOfNumber(CharSequence summary) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5580afe..78282fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -38,7 +39,9 @@
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Track status of Wi-Fi for the Sys UI.
@@ -50,6 +53,7 @@
     private final NetworkScoreManager mNetworkScoreManager;
     private final ConnectivityManager mConnectivityManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Set<Integer> mNetworks = new HashSet<>();
     private final WifiNetworkScoreCache.CacheListener mCacheListener =
             new WifiNetworkScoreCache.CacheListener(mHandler) {
                 @Override
@@ -64,6 +68,20 @@
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(
+                Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            boolean isVcnOverWifi =
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                            && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
+            boolean isWifi =
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+            if (isVcnOverWifi || isWifi) {
+                mNetworks.add(network.getNetId());
+            }
+        }
+
         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
         // and onLinkPropertiesChanged.
         @Override
@@ -84,9 +102,12 @@
 
         @Override
         public void onLost(Network network) {
-            updateWifiInfo(null);
-            updateStatusLabel();
-            mCallback.run();
+            if (mNetworks.contains(network.getNetId())) {
+                mNetworks.remove(network.getNetId());
+                updateWifiInfo(null);
+                updateStatusLabel();
+                mCallback.run();
+            }
         }
     };
     private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
index 85e2174..1a8477d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
@@ -18,11 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+
 import android.content.Context;
 import android.text.SpannedString;
 import android.text.style.RelativeSizeSpan;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
@@ -97,4 +101,30 @@
                 .findViewById(android.R.id.progress);
         assertThat(progressBar.getProgress()).isEqualTo((int) (31.0f / 80 * 100));
     }
+
+    @Test
+    public void setCustomContent_setNullImageView_noChild() {
+        mUsageProgressBarPreference.setCustomContent(null /* imageView */);
+
+        mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        final FrameLayout customContent =
+                (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+        assertThat(customContent.getChildCount()).isEqualTo(0);
+        assertThat(customContent.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setCustomContent_setImageView_oneChild() {
+        final ImageView imageView = mock(ImageView.class);
+        mUsageProgressBarPreference.setCustomContent(imageView);
+
+        mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        final FrameLayout customContent =
+                (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+        assertThat(customContent.getChildCount()).isEqualTo(1);
+        assertThat(customContent.getChildAt(0)).isEqualTo(imageView);
+        assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
+    }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efd941e..438cec8 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
+                    Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
@@ -280,6 +281,7 @@
                     Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
                     Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
                     Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+                    Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.FANCY_IME_ANIMATIONS,
                     Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
                     Settings.Global.FORCED_APP_STANDBY_ENABLED,
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
index cf64136..5cb6f46 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_camera" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
           android:drawable="@*android:drawable/perm_group_camera"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
index 0a6a4a3..bff9b4b 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_microphone_location" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
-          android:drawable="@*android:drawable/perm_group_microphone"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
+          android:drawable="@*android:drawable/perm_group_location"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
index 0a6a4a3..28466c8 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_microphone_location" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
           android:drawable="@*android:drawable/perm_group_microphone"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
index 71b414f..93664da 100644
--- a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
     android:layout_marginBottom="@dimen/screenshot_offset_y"
     android:scaleType="fitStart"
     android:elevation="@dimen/screenshot_preview_elevation"
-    android:visibility="gone"
+    android:visibility="invisible"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
     android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 5db247e..4d77a0d 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -22,7 +22,13 @@
     android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins"
     android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
+    android:layout_marginTop="8dp"
     android:orientation="vertical"
-    android:padding = "8dp"
+    android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
+    android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
+    android:paddingBottom="12dp"
+    android:paddingTop="8dp"
     android:background="@drawable/privacy_dialog_bg"
-/>
\ No newline at end of file
+/>
+<!-- 12dp padding bottom so there's 20dp total under the icon -->
+<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 882e968..91ffe22 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -16,19 +16,22 @@
   -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/privacy_item"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="48dp"
     android:orientation="horizontal"
-    android:layout_marginTop="8dp"
+    android:layout_marginTop="4dp"
+    android:importantForAccessibility="yes"
+    android:focusable="true"
     >
+    <!-- 4dp marginTop makes 20dp minimum between icons -->
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
+        android:layout_width="@dimen/ongoing_appops_dialog_circle_size"
+        android:layout_height="@dimen/ongoing_appops_dialog_circle_size"
         android:layout_gravity="center_vertical"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
+        android:importantForAccessibility="no"
     />
 
     <TextView
@@ -38,25 +41,21 @@
         android:layout_width="0dp"
         android:layout_weight="1"
         android:layout_gravity="center_vertical"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:textDirection="locale"
+        android:textAlignment="viewStart"
         android:gravity="start | center_vertical"
-        android:textAppearance="@style/TextAppearance.QS.TileLabel"
+        android:textAppearance="@style/TextAppearance.PrivacyDialog"
+        android:lineHeight="20sp"
     />
 
-    <FrameLayout
-        android:id="@+id/link"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+    <ImageView
+        android:layout_height="24dp"
+        android:layout_width="24dp"
         android:layout_gravity="center_vertical"
-        android:background="?android:attr/selectableItemBackground"
-    >
-
-        <ImageView
-            android:layout_height="24dp"
-            android:layout_width="24dp"
-            android:layout_gravity="center"
-            android:src="@*android:drawable/ic_chevron_end"
-            android:tint="?android:attr/textColorPrimary"
-            />
-    </FrameLayout>
+        android:src="@*android:drawable/ic_chevron_end"
+        android:tint="?android:attr/textColorPrimary"
+        />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e510930..1fac96b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1181,6 +1181,12 @@
 
     <dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
 
+    <dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
+
+    <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+
+    <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
+
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 8c6aba2..01e54ff 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,4 +33,7 @@
     <bool name="flag_keyguard_layout">false</bool>
 
     <bool name="flag_brightness_slider">false</bool>
+
+    <!-- People Tile flag -->
+    <bool name="flag_conversations">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4baa06a..eee0128 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1888,7 +1888,7 @@
     <!-- Notification Inline controls: describes how the notification was adjusted [CHAR_LIMIT=NONE] -->
     <string name="feedback_demoted">This notification was automatically &lt;b>ranked lower&lt;/b> in your shade.</string>
     <!-- Notification Inline controls: prompts the user for feedback [CHAR_LIMIT=NONE] -->
-    <string name="feedback_prompt">Was this correct?</string>
+    <string name="feedback_prompt">Let the developer know your feedback. Was this correct?</string>
     <!-- Notification Inline controls: responds to user provided feedback [CHAR_LIMIT=NONE] -->
     <string name="feedback_response">Thanks for your feedback!</string>
     <string name="feedback_ok">OK</string>
@@ -2556,8 +2556,8 @@
     <!-- Text for privacy dialog, indicating that the application is the enterprise version [CHAR LIMIT=NONE] -->
     <string name="ongoing_privacy_dialog_enterprise">(enterprise)</string>
 
-    <!-- Text for privacy dialog, identifying the phonecall app [CHAR LIMIT=NONE]-->
-    <string name="ongoing_privacy_dialog_phonecall">Phonecall</string>
+    <!-- Text for privacy dialog, identifying the phone call app [CHAR LIMIT=NONE]-->
+    <string name="ongoing_privacy_dialog_phonecall">Phone call</string>
 
     <!-- Text for privacy dialog, indicating that an app is using an op on behalf of another [CHAR LIMIT=NONE] -->
     <string name="ongoing_privacy_dialog_attribution_text">(through <xliff:g id="attribution" example="Special app">%s</xliff:g>)</string>
@@ -2771,13 +2771,6 @@
     <!-- Controls menu, edit [CHAR_LIMIT=30] -->
     <string name="controls_menu_edit">Edit controls</string>
 
-    <!-- Device-specific path to the node for enabling/disabling the high-brightness mode -->
-    <string name="udfps_hbm_sysfs_path" translatable="false"></string>
-    <!-- Device-specific payload for enabling the high-brightness mode -->
-    <string name="udfps_hbm_enable_command" translatable="false"></string>
-    <!-- Device-specific payload for disabling the high-brightness mode -->
-    <string name="udfps_hbm_disable_command" translatable="false"></string>
-
     <!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_add_output">Add outputs</string>
     <!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index db260ce..ad4e78e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -745,4 +745,9 @@
           * Title: headline, medium 20sp
           * Message: body, 16 sp -->
     <style name="Theme.ControlsRequestDialog" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert"/>
+
+    <style name="TextAppearance.PrivacyDialog">
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 2a0715e..87f6b82 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -118,9 +118,9 @@
     }
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
-        final RemoteAnimationTargetCompat[] appsCompat =
-                new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
-        for (int i = 0; i < apps.length; i++) {
+        final int length = apps != null ? apps.length : 0;
+        final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
+        for (int i = 0; i < length; i++) {
             appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
         }
         return appsCompat;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index cc59c39..30db341 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -395,7 +395,9 @@
     }
 
     private void beginJankInstrument(int cuj) {
-        InteractionJankMonitor.getInstance().begin(mSecurityViewFlipper.getSecurityView(), cuj);
+        KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
+        if (securityView == null) return;
+        InteractionJankMonitor.getInstance().begin(securityView, cuj);
     }
 
     private void endJankInstrument(int cuj) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9908e67..d9a1eb6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1909,12 +1909,14 @@
     }
 
     /**
-     * Whether to show the lock icon on lock screen and bouncer. This depends on the enrolled
-     * biometrics to the device.
+     * Whether to show the lock icon on lock screen and bouncer.
      */
-    public boolean shouldShowLockIcon() {
-        return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
-                && !isUdfpsEnrolled();
+    public boolean canShowLockIcon() {
+        if (mLockScreenMode == LOCK_SCREEN_MODE_LAYOUT_1) {
+            return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
+                    && !isUdfpsEnrolled();
+        }
+        return true;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 036fcf3..78f7966 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -127,7 +127,7 @@
                     }
 
                     // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
-                    // TODO(b/170396074): Remove this when we don't need a widget anymore.
+                    // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
                     try {
                         int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
                                 Settings.Global.SHOW_PEOPLE_SPACE, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 66c535f..3f79be8 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowInsets
 import android.widget.ImageView
@@ -66,9 +65,8 @@
         super.onCreate(savedInstanceState)
         window?.apply {
             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
-            setLayout(MATCH_PARENT, WRAP_CONTENT)
+            setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
-            setBackgroundDrawable(null)
         }
 
         setContentView(R.layout.privacy_dialog)
@@ -125,12 +123,12 @@
         val finalText = element.attribution?.let {
             TextUtils.concat(
                     firstLine,
-                    "\n",
+                    " ",
                     context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
             )
         } ?: firstLine
         newView.requireViewById<TextView>(R.id.text).text = finalText
-        newView.requireViewById<View>(R.id.link).apply {
+        newView.apply {
             tag = element.type.permGroupName
             setOnClickListener(clickListener)
         }
@@ -154,9 +152,7 @@
     }
 
     private val clickListener = View.OnClickListener { v ->
-        if (v.id == R.id.link) {
-            v.tag?.let { activityStarter(it as String) }
-        }
+        v.tag?.let { activityStarter(it as String) }
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index 99e9bfc..8b6c04f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -227,21 +227,20 @@
     /**
      * Filters the list of elements to show.
      *
-     * * Return at most one element per [PrivacyType], sorted by the natural order of the
-     * [PrivacyType].
-     * * If there are no active usages for a type, return the most recent
-     * * If there are multiple active usages for a type, return the most active recent.
+     * For each privacy type, it'll return all active elements. If there are no active elements,
+     * it'll return the most recent access
      */
     private fun filterAndSelect(
         list: List<PrivacyDialog.PrivacyElement>
     ): List<PrivacyDialog.PrivacyElement> {
-        return list.groupBy { it.type }.toSortedMap().mapNotNull { entry ->
-            if (entry.value.isEmpty()) {
-                null
+        return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
+            val actives = elements.filter { it.active }
+            if (actives.isNotEmpty()) {
+                actives.sortedByDescending { it.lastActiveTimestamp }
             } else {
-                val actives = entry.value.filter { it.active }
-                val out = if (actives.isNotEmpty()) actives else entry.value
-                out.maxByOrNull { it.lastActiveTimestamp }
+                elements.maxByOrNull { it.lastActiveTimestamp }?.let {
+                    listOf(it)
+                } ?: emptyList()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d6413ed..0a7eea4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -602,7 +602,7 @@
     private void runScrollCapture(ScrollCaptureClient.Connection connection) {
         cancelTimeout();
         ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
-                mMainExecutor, mBgExecutor, mImageExporter);
+                mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger);
         controller.attach(mWindow);
         controller.start(new TakeScreenshotService.RequestCallback() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index f1fb688..5cf0188 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -63,7 +63,15 @@
     @UiEvent(doc = "screenshot swiped to dismiss")
     SCREENSHOT_SWIPE_DISMISSED(656),
     @UiEvent(doc = "screenshot reentered for new screenshot")
-    SCREENSHOT_REENTERED(640);
+    SCREENSHOT_REENTERED(640),
+    @UiEvent(doc = "Long screenshot button was shown to the user")
+    SCREENSHOT_LONG_SCREENSHOT_IMPRESSION(687),
+    @UiEvent(doc = "User has requested a long screenshot")
+    SCREENSHOT_LONG_SCREENSHOT_REQUESTED(688),
+    @UiEvent(doc = "User has shared a long screenshot")
+    SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
+    @UiEvent(doc = "User has sent a long screenshot to the editor")
+    SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index bf86b68..3bc5ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -192,14 +192,14 @@
         if (DEBUG_SCROLL) {
             Log.d(TAG, "Showing Scroll option");
         }
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
         mScrollChip.setVisibility(VISIBLE);
         mScrollChip.setOnClickListener((v) -> {
             if (DEBUG_INPUT) {
                 Log.d(TAG, "scroll chip tapped");
             }
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
             onClick.run();
-            // TODO Logging, store event consumer to a field
-            //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 9be3566..176a2c7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -29,6 +29,7 @@
 import android.view.Window;
 import android.widget.ImageView;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -58,6 +59,7 @@
     private final Executor mBgExecutor;
     private final ImageExporter mImageExporter;
     private final ImageTileSet mImageTileSet;
+    private final UiEventLogger mUiEventLogger;
 
     private ZonedDateTime mCaptureTime;
     private UUID mRequestId;
@@ -72,12 +74,13 @@
     private Runnable mPendingAction;
 
     public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
-            Executor bgExecutor, ImageExporter exporter) {
+            Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
         mContext = context;
         mConnection = connection;
         mUiExecutor = uiExecutor;
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
+        mUiEventLogger = uiEventLogger;
         mImageTileSet = new ImageTileSet();
     }
 
@@ -136,10 +139,12 @@
             disableButtons();
             finish();
         } else if (id == R.id.edit) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
             v.setPressed(true);
             disableButtons();
             edit();
         } else if (id == R.id.share) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
             v.setPressed(true);
             disableButtons();
             share();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index c54c459..e7b60c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -66,4 +66,8 @@
     public boolean useNewBrightnessSlider() {
         return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
     }
+
+    public boolean isPeopleTileEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_conversations);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 1ec043c..e4ae560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -23,8 +23,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -41,7 +39,6 @@
 
     private final Context mContext;
     private final InjectionInflationController mInjectionInflationController;
-    private final LockscreenLockIconController mLockIconController;
     private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
 
     private NotificationShadeWindowView mNotificationShadeWindowView;
@@ -51,11 +48,9 @@
     @Inject
     public SuperStatusBarViewFactory(Context context,
             InjectionInflationController injectionInflationController,
-            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
-            LockscreenLockIconController lockIconController) {
+            NotificationShelfComponent.Builder notificationShelfComponentBuilder) {
         mContext = context;
         mInjectionInflationController = injectionInflationController;
-        mLockIconController = lockIconController;
         mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
     }
 
@@ -77,10 +72,6 @@
             throw new IllegalStateException(
                     "R.layout.super_notification_shade could not be properly inflated");
         }
-        LockIcon lockIcon = mNotificationShadeWindowView.findViewById(R.id.lock_icon);
-        if (lockIcon != null) {
-            mLockIconController.attach(lockIcon);
-        }
 
         return mNotificationShadeWindowView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 4fde118..db49e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -21,7 +21,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -50,7 +49,6 @@
     private final ShadeListBuilder mListBuilder;
     private final NotifCoordinators mNotifPluggableCoordinators;
     private final NotifInflaterImpl mNotifInflater;
-    private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
     private final DumpManager mDumpManager;
     private final ShadeViewManagerFactory mShadeViewManagerFactory;
     private final FeatureFlags mFeatureFlags;
@@ -64,7 +62,6 @@
             ShadeListBuilder listBuilder,
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
-            PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             DumpManager dumpManager,
             ShadeViewManagerFactory shadeViewManagerFactory,
             FeatureFlags featureFlags) {
@@ -75,7 +72,6 @@
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
-        mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mShadeViewManagerFactory = shadeViewManagerFactory;
         mFeatureFlags = featureFlags;
     }
@@ -103,7 +99,6 @@
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
         mGroupCoalescer.attach(notificationService);
-        mPeopleSpaceWidgetManager.attach(notificationService);
 
         Log.d(TAG, "Notif pipeline initialized");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 54ce4ed..0ad6507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,6 +18,7 @@
 
 import android.service.notification.StatusBarNotification
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationListener
@@ -73,7 +74,8 @@
     private val headsUpController: HeadsUpController,
     private val headsUpViewBinder: HeadsUpViewBinder,
     private val clickerBuilder: NotificationClicker.Builder,
-    private val animatedImageNotificationManager: AnimatedImageNotificationManager
+    private val animatedImageNotificationManager: AnimatedImageNotificationManager,
+    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager
 ) : NotificationsController {
 
     override fun initialize(
@@ -126,6 +128,10 @@
 
             entryManager.attach(notificationListener)
         }
+
+        if (featureFlags.isPeopleTileEnabled) {
+            peopleSpaceWidgetManager.attach(notificationListener)
+        }
     }
 
     override fun dump(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index e1eaf3c..f289b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -71,7 +71,7 @@
             "persist.sysui.wake_performs_auth", true);
     private boolean mDozingRequested;
     private boolean mPulsing;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
@@ -86,9 +86,8 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
-    private final LockscreenLockIconController mLockscreenLockIconController;
     private final AuthController mAuthController;
-    private NotificationIconAreaController mNotificationIconAreaController;
+    private final NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private NotificationPanelViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
@@ -109,7 +108,6 @@
             PulseExpansionHandler pulseExpansionHandler,
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            LockscreenLockIconController lockscreenLockIconController,
             AuthController authController,
             NotificationIconAreaController notificationIconAreaController) {
         super();
@@ -129,7 +127,6 @@
         mPulseExpansionHandler = pulseExpansionHandler;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
-        mLockscreenLockIconController = lockscreenLockIconController;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index eceac32..4b70de9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,12 +37,10 @@
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -52,18 +50,20 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
 
 import java.util.Optional;
 
 import javax.inject.Inject;
 
-/** Controls the {@link LockIcon} in the lockscreen. */
-@SysUISingleton
-public class LockscreenLockIconController {
+/** Controls the {@link LockIcon} on the lockscreen. */
+@StatusBarComponent.StatusBarScope
+public class LockscreenLockIconController extends ViewController<LockIcon> {
 
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -79,7 +79,6 @@
     private final KeyguardStateController mKeyguardStateController;
     private final Resources mResources;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
-    private final KeyguardSecurityModel mKeyguardSecurityModel;
     private boolean mKeyguardShowing;
     private boolean mKeyguardJustShown;
     private boolean mBlockUpdates;
@@ -92,50 +91,310 @@
     private boolean mBouncerShowingScrimmed;
     private boolean mFingerprintUnlock;
     private int mStatusBarState = StatusBarState.SHADE;
-    private LockIcon mLockIcon;
+    private int mLastState;
+    private boolean mDozing;
 
-    private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
-            new View.OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            mStatusBarStateController.addCallback(mSBStateListener);
-            mConfigurationController.addCallback(mConfigurationListener);
-            mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
-            mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
-            mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+    @Inject
+    public LockscreenLockIconController(
+            @Nullable LockIcon view,
+            LockscreenGestureLogger lockscreenGestureLogger,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            LockPatternUtils lockPatternUtils,
+            ShadeController shadeController,
+            AccessibilityController accessibilityController,
+            KeyguardIndicationController keyguardIndicationController,
+            StatusBarStateController statusBarStateController,
+            ConfigurationController configurationController,
+            NotificationWakeUpCoordinator notificationWakeUpCoordinator,
+            KeyguardBypassController keyguardBypassController,
+            @Nullable DockManager dockManager,
+            KeyguardStateController keyguardStateController,
+            @Main Resources resources,
+            HeadsUpManagerPhone headsUpManagerPhone) {
+        super(view);
+        mLockscreenGestureLogger = lockscreenGestureLogger;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
+        mShadeController = shadeController;
+        mAccessibilityController = accessibilityController;
+        mKeyguardIndicationController = keyguardIndicationController;
+        mStatusBarStateController = statusBarStateController;
+        mConfigurationController = configurationController;
+        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+        mKeyguardBypassController = keyguardBypassController;
+        mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
+        mKeyguardStateController = keyguardStateController;
+        mResources = resources;
+        mHeadsUpManagerPhone = headsUpManagerPhone;
 
-            mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+        if (view == null) {
+            return;
+        }
 
-            mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
-            mConfigurationListener.onThemeChanged();
+        mKeyguardIndicationController.setLockIconController(this);
+    }
 
-            updateColor();
+    @Override
+    protected void onInit() {
+        if (mView == null) {
+            return;
+        }
+        mView.setOnClickListener(this::handleClick);
+        mView.setOnLongClickListener(this::handleLongClick);
+        mView.setAccessibilityDelegate(mAccessibilityDelegate);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        setStatusBarState(mStatusBarStateController.getState());
+        mDozing = mStatusBarStateController.isDozing();
+        mStatusBarStateController.addCallback(mSBStateListener);
+        mConfigurationController.addCallback(mConfigurationListener);
+        mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+
+        mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+
+        mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
+        mConfigurationListener.onThemeChanged();
+
+        updateColor();
+        update();
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mStatusBarStateController.removeCallback(mSBStateListener);
+        mConfigurationController.removeCallback(mConfigurationListener);
+        mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+        mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
+
+        mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+    }
+
+    /**
+     * Called whenever the scrims become opaque, transparent or semi-transparent.
+     */
+    public void onScrimVisibilityChanged(Integer scrimsVisible) {
+        if (mWakeAndUnlockRunning
+                && scrimsVisible == ScrimController.TRANSPARENT) {
+            mWakeAndUnlockRunning = false;
             update();
         }
+    }
 
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mStatusBarStateController.removeCallback(mSBStateListener);
-            mConfigurationController.removeCallback(mConfigurationListener);
-            mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
-            mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
-            mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
-
-            mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+    /**
+     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+     * icon on top of the black front scrim.
+     * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
+     * keyguard with fingerprint.
+     * @param wakeAndUnlock are we wake and unlocking
+     * @param isUnlock are we currently unlocking
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
+            BiometricSourceType type) {
+        if (wakeAndUnlock) {
+            mWakeAndUnlockRunning = true;
         }
-    };
+        mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
+        if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
+                && canBlockUpdates()) {
+            // We don't want the icon to change while we are unlocking
+            mBlockUpdates = true;
+        }
+        update();
+    }
+
+    /**
+     * When we're launching an affordance, like double pressing power to open camera.
+     */
+    public void onShowingLaunchAffordanceChanged(Boolean showing) {
+        mShowingLaunchAffordance = showing;
+        update();
+    }
+
+    /** Sets whether the bouncer is showing. */
+    public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
+        mBouncerShowingScrimmed = scrimmed;
+        update();
+    }
+
+    /**
+     * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
+     * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
+     */
+    public void setBouncerHideAmount(float hideAmount) {
+        mBouncerHiddenAmount = hideAmount;
+        updateColor();
+    }
+
+    private void updateColor() {
+        if (mView == null) {
+            return;
+        }
+        int iconColor = -1;
+        if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
+            TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+            iconColor = typedArray.getColor(0, Color.WHITE);
+            typedArray.recycle();
+        } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
+            iconColor = Utils.getColorAttrDefaultColor(
+                    mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+        } else {
+            // bouncer is transitioning
+            TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+            int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
+            typedArray.recycle();
+            int keyguardIconColor = Utils.getColorAttrDefaultColor(
+                    mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+            iconColor = (int) new ArgbEvaluator().evaluate(
+                    mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
+        }
+        mView.updateColor(iconColor);
+    }
+
+    /**
+     * Animate padlock opening when bouncer challenge is solved.
+     */
+    public void onBouncerPreHideAnimation() {
+        update();
+    }
+
+    /**
+     * If we're currently presenting an authentication error message.
+     */
+    public void setTransientBiometricsError(boolean transientBiometricsError) {
+        mTransientBiometricsError = transientBiometricsError;
+        update();
+    }
+
+    private boolean handleLongClick(View view) {
+        mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
+                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
+        mKeyguardIndicationController.showTransientIndication(
+                R.string.keyguard_indication_trust_disabled);
+        mKeyguardUpdateMonitor.onLockIconPressed();
+        mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+
+        return true;
+    }
+
+
+    private void handleClick(View view) {
+        if (!mAccessibilityController.isAccessibilityEnabled()) {
+            return;
+        }
+        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+    }
+
+    private void update() {
+        update(false /* force */);
+    }
+
+    private void update(boolean force) {
+        if (mView == null) {
+            return;
+        }
+        int state = getState();
+        boolean shouldUpdate = mLastState != state || force;
+        if (mBlockUpdates && canBlockUpdates()) {
+            shouldUpdate = false;
+        }
+        if (shouldUpdate && mView.getVisibility() != GONE) {
+            mView.update(state, mDozing, mKeyguardJustShown);
+        }
+        mLastState = state;
+        mKeyguardJustShown = false;
+        updateIconVisibility();
+        updateClickability();
+    }
+
+    private int getState() {
+        if ((mKeyguardStateController.canDismissLockScreen()
+                || !mKeyguardStateController.isShowing()
+                || mKeyguardStateController.isKeyguardGoingAway()
+                || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
+            return STATE_LOCK_OPEN;
+        } else if (mTransientBiometricsError) {
+            return STATE_BIOMETRICS_ERROR;
+        } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
+            return STATE_SCANNING_FACE;
+        } else {
+            return STATE_LOCKED;
+        }
+    }
+
+    private boolean canBlockUpdates() {
+        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+    }
+
+    /** Set the StatusBarState. */
+    private void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+        updateIconVisibility();
+    }
+
+    /**
+     * Update the icon visibility
+     * @return true if the visibility changed
+     */
+    private boolean updateIconVisibility() {
+        if (mView == null) {
+            return false;
+        }
+        if (!mKeyguardUpdateMonitor.canShowLockIcon()) {
+            boolean changed = mView.getVisibility() != GONE;
+            mView.setVisibility(GONE);
+            return changed;
+        }
+
+        boolean onAodOrDocked = mDozing || mDocked;
+        boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
+        boolean fingerprintOrBypass = mFingerprintUnlock
+                || mKeyguardBypassController.getBypassEnabled();
+        if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
+            if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
+                    || mHeadsUpManagerPhone.hasPinnedHeadsUp()
+                    || mStatusBarState == StatusBarState.KEYGUARD
+                    || mStatusBarState == StatusBarState.SHADE)
+                    && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
+                invisible = true;
+            }
+        }
+        return mView.updateIconVisibility(!invisible);
+    }
+
+    private void updateClickability() {
+        if (mView == null) {
+            return;
+        }
+        boolean canLock = mKeyguardStateController.isMethodSecure()
+                && mKeyguardStateController.canDismissLockScreen();
+        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
+        mView.setClickable(clickToUnlock);
+        mView.setLongClickable(canLock && !clickToUnlock);
+        mView.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+    }
 
     private final StatusBarStateController.StateListener mSBStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onDozingChanged(boolean isDozing) {
-                    setDozing(isDozing);
+                    if (mDozing != isDozing) {
+                        mDozing = isDozing;
+                        update();
+                    }
                 }
 
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
-                    if (mLockIcon != null) {
-                        mLockIcon.setDozeAmount(eased);
+                    if (mView != null) {
+                        mView.setDozeAmount(eased);
                     }
                 }
 
@@ -160,29 +419,21 @@
 
         @Override
         public void onDensityOrFontScaleChanged() {
-            if (mLockIcon == null) {
-                return;
-            }
-
-            ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
+            ViewGroup.LayoutParams lp = mView.getLayoutParams();
             if (lp == null) {
                 return;
             }
-            lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
-            lp.height = mLockIcon.getResources().getDimensionPixelSize(
+            lp.width = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
+            lp.height = mView.getResources().getDimensionPixelSize(
                     R.dimen.keyguard_lock_height);
-            mLockIcon.setLayoutParams(lp);
+            mView.setLayoutParams(lp);
             update(true /* force */);
         }
 
         @Override
         public void onLocaleListChanged() {
-            if (mLockIcon == null) {
-                return;
-            }
-
-            mLockIcon.setContentDescription(
-                    mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
+            mView.setContentDescription(
+                    mView.getResources().getText(R.string.accessibility_unlock_button));
             update(true /* force */);
         }
 
@@ -299,9 +550,9 @@
                     if (fingerprintRunning && unlockingAllowed) {
                         AccessibilityNodeInfo.AccessibilityAction unlock =
                                 new AccessibilityNodeInfo.AccessibilityAction(
-                                AccessibilityNodeInfo.ACTION_CLICK,
-                                mResources.getString(
-                                        R.string.accessibility_unlock_without_fingerprint));
+                                        AccessibilityNodeInfo.ACTION_CLICK,
+                                        mResources.getString(
+                                                R.string.accessibility_unlock_without_fingerprint));
                         info.addAction(unlock);
                         info.setHintText(mResources.getString(
                                 R.string.accessibility_waiting_for_fingerprint));
@@ -313,277 +564,4 @@
                     }
                 }
             };
-    private int mLastState;
-
-    @Inject
-    public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            LockPatternUtils lockPatternUtils,
-            ShadeController shadeController,
-            AccessibilityController accessibilityController,
-            KeyguardIndicationController keyguardIndicationController,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            @Nullable DockManager dockManager,
-            KeyguardStateController keyguardStateController,
-            @Main Resources resources,
-            HeadsUpManagerPhone headsUpManagerPhone,
-            KeyguardSecurityModel keyguardSecurityModel) {
-        mLockscreenGestureLogger = lockscreenGestureLogger;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mLockPatternUtils = lockPatternUtils;
-        mShadeController = shadeController;
-        mAccessibilityController = accessibilityController;
-        mKeyguardIndicationController = keyguardIndicationController;
-        mStatusBarStateController = statusBarStateController;
-        mConfigurationController = configurationController;
-        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
-        mKeyguardBypassController = keyguardBypassController;
-        mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
-        mKeyguardStateController = keyguardStateController;
-        mResources = resources;
-        mHeadsUpManagerPhone = headsUpManagerPhone;
-        mKeyguardSecurityModel = keyguardSecurityModel;
-
-        mKeyguardIndicationController.setLockIconController(this);
-    }
-
-    /**
-     * Associate the controller with a {@link LockIcon}
-     *
-     * TODO: change to an init method and inject the view.
-     */
-    public void attach(LockIcon lockIcon) {
-        mLockIcon = lockIcon;
-
-        mLockIcon.setOnClickListener(this::handleClick);
-        mLockIcon.setOnLongClickListener(this::handleLongClick);
-        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
-
-        if (mLockIcon.isAttachedToWindow()) {
-            mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
-        }
-        mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        setStatusBarState(mStatusBarStateController.getState());
-    }
-
-    public LockIcon getView() {
-        return mLockIcon;
-    }
-
-    /**
-     * Called whenever the scrims become opaque, transparent or semi-transparent.
-     */
-    public void onScrimVisibilityChanged(Integer scrimsVisible) {
-        if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.TRANSPARENT) {
-            mWakeAndUnlockRunning = false;
-            update();
-        }
-    }
-
-    /**
-     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
-     * icon on top of the black front scrim.
-     * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
-     * keyguard with fingerprint.
-     * @param wakeAndUnlock are we wake and unlocking
-     * @param isUnlock are we currently unlocking
-     */
-    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
-            BiometricSourceType type) {
-        if (wakeAndUnlock) {
-            mWakeAndUnlockRunning = true;
-        }
-        mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
-        if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
-                && canBlockUpdates()) {
-            // We don't want the icon to change while we are unlocking
-            mBlockUpdates = true;
-        }
-        update();
-    }
-
-    /**
-     * When we're launching an affordance, like double pressing power to open camera.
-     */
-    public void onShowingLaunchAffordanceChanged(Boolean showing) {
-        mShowingLaunchAffordance = showing;
-        update();
-    }
-
-    /** Sets whether the bouncer is showing. */
-    public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
-        mBouncerShowingScrimmed = scrimmed;
-        update();
-    }
-
-    /**
-     * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
-     * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
-     */
-    public void setBouncerHideAmount(float hideAmount) {
-        mBouncerHiddenAmount = hideAmount;
-        updateColor();
-    }
-
-    private void updateColor() {
-        if (mLockIcon == null) {
-            return;
-        }
-
-        int iconColor = -1;
-        if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
-            TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
-                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
-            iconColor = typedArray.getColor(0, Color.WHITE);
-            typedArray.recycle();
-        } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
-            iconColor = Utils.getColorAttrDefaultColor(
-                    mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
-        } else {
-            // bouncer is transitioning
-            TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
-                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
-            int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
-            typedArray.recycle();
-            int keyguardIconColor = Utils.getColorAttrDefaultColor(
-                    mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
-            iconColor = (int) new ArgbEvaluator().evaluate(
-                    mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
-        }
-        mLockIcon.updateColor(iconColor);
-    }
-
-    /**
-     * Animate padlock opening when bouncer challenge is solved.
-     */
-    public void onBouncerPreHideAnimation() {
-        update();
-    }
-
-    /**
-     * If we're currently presenting an authentication error message.
-     */
-    public void setTransientBiometricsError(boolean transientBiometricsError) {
-        mTransientBiometricsError = transientBiometricsError;
-        update();
-    }
-
-    private boolean handleLongClick(View view) {
-        mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
-                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
-        mKeyguardIndicationController.showTransientIndication(
-                R.string.keyguard_indication_trust_disabled);
-        mKeyguardUpdateMonitor.onLockIconPressed();
-        mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
-
-        return true;
-    }
-
-
-    private void handleClick(View view) {
-        if (!mAccessibilityController.isAccessibilityEnabled()) {
-            return;
-        }
-        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-    }
-
-    private void update() {
-        update(false /* force */);
-    }
-
-    private void update(boolean force) {
-        int state = getState();
-        boolean shouldUpdate = mLastState != state || force;
-        if (mBlockUpdates && canBlockUpdates()) {
-            shouldUpdate = false;
-        }
-        if (shouldUpdate && mLockIcon != null && mLockIcon.getVisibility() != GONE) {
-            mLockIcon.update(state,
-                    mStatusBarStateController.isDozing(), mKeyguardJustShown);
-        }
-        mLastState = state;
-        mKeyguardJustShown = false;
-        updateIconVisibility();
-        updateClickability();
-    }
-
-    private int getState() {
-        if ((mKeyguardStateController.canDismissLockScreen()
-                || !mKeyguardStateController.isShowing()
-                || mKeyguardStateController.isKeyguardGoingAway()
-                || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
-            return STATE_LOCK_OPEN;
-        } else if (mTransientBiometricsError) {
-            return STATE_BIOMETRICS_ERROR;
-        } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
-            return STATE_SCANNING_FACE;
-        } else {
-            return STATE_LOCKED;
-        }
-    }
-
-    private boolean canBlockUpdates() {
-        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
-    }
-
-    private void setDozing(boolean isDozing) {
-        update();
-    }
-
-    /** Set the StatusBarState. */
-    private void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
-        updateIconVisibility();
-    }
-
-    /**
-     * Update the icon visibility
-     * @return true if the visibility changed
-     */
-    private boolean updateIconVisibility() {
-        if (mLockIcon == null) {
-            return false;
-        }
-
-        if (!mKeyguardUpdateMonitor.shouldShowLockIcon()) {
-            boolean changed = mLockIcon.getVisibility() != GONE;
-            mLockIcon.setVisibility(GONE);
-            return changed;
-        }
-
-        boolean onAodOrDocked = mStatusBarStateController.isDozing() || mDocked;
-        boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
-        boolean fingerprintOrBypass = mFingerprintUnlock
-                || mKeyguardBypassController.getBypassEnabled();
-        if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
-            if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
-                    || mHeadsUpManagerPhone.hasPinnedHeadsUp()
-                    || mStatusBarState == StatusBarState.KEYGUARD
-                    || mStatusBarState == StatusBarState.SHADE)
-                    && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
-                invisible = true;
-            }
-        }
-        return mLockIcon.updateIconVisibility(!invisible);
-    }
-
-    private void updateClickability() {
-        if (mAccessibilityController == null) {
-            return;
-        }
-        boolean canLock = mKeyguardStateController.isMethodSecure()
-                && mKeyguardStateController.canDismissLockScreen();
-        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
-        if (mLockIcon != null) {
-            mLockIcon.setClickable(clickToUnlock);
-            mLockIcon.setLongClickable(canLock && !clickToUnlock);
-            mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
-        }
-    }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index b24d0e7..a5284f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -910,7 +910,7 @@
                     clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
-                    mUpdateMonitor.shouldShowLockIcon(),
+                    mUpdateMonitor.canShowLockIcon(),
                     getQsExpansionFraction(),
                     mDisplayCutoutTopInset);
             mClockPositionAlgorithm.run(mClockPositionResult);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8365139..4a3d8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -368,7 +368,6 @@
     protected NotificationShadeWindowController mNotificationShadeWindowController;
     protected StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final LockscreenLockIconController mLockscreenLockIconController;
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
@@ -415,6 +414,7 @@
     // expanded notifications
     // the sliding/resizing panel within the notification window
     protected NotificationPanelViewController mNotificationPanelViewController;
+    protected LockscreenLockIconController mLockscreenLockIconController;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -725,7 +725,6 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
-            LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             @Nullable KeyguardLiftController keyguardLiftController,
@@ -807,7 +806,6 @@
         mAssistManagerLazy = assistManagerLazy;
         mConfigurationController = configurationController;
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mLockscreenLockIconController = lockscreenLockIconController;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
@@ -1173,9 +1171,7 @@
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
             mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
-            if (mNotificationShadeWindowView != null) {
-                mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
-            }
+            mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
@@ -1208,9 +1204,6 @@
             createUserSwitcher();
         }
 
-        mNotificationPanelViewController.setLaunchAffordanceListener(
-                mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
-
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
         if (container != null) {
@@ -1490,6 +1483,11 @@
         mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
         mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
         mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
+        mLockscreenLockIconController = statusBarComponent.getLockscreenLockIconController();
+        mLockscreenLockIconController.init();
+
+        mNotificationPanelViewController.setLaunchAffordanceListener(
+                mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
     }
 
     protected void startKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 5f90077..f6165f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -316,11 +316,23 @@
 
         mIconController.removeAllIconsForSlot(mSlotMobile);
         mMobileStates.clear();
+        List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
+        noCallingStates.addAll(mNoCallingStates);
         mNoCallingStates.clear();
         final int n = subs.size();
         for (int i = 0; i < n; i++) {
             mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
-            mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+            boolean isNewSub = true;
+            for (NoCallingIconState state : noCallingStates) {
+                if (state.subId == subs.get(i).getSubscriptionId()) {
+                    mNoCallingStates.add(state);
+                    isNewSub = false;
+                    break;
+                }
+            }
+            if (isNewSub) {
+                mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 802da3e..ecd9613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -18,6 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
@@ -73,4 +74,9 @@
     @StatusBarScope
     NotificationPanelViewController getNotificationPanelViewController();
 
+    /**
+     * Creates a LockscreenLockIconController.
+     */
+    @StatusBarScope
+    LockscreenLockIconController getLockscreenLockIconController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 26e1959..9e9533d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -77,7 +77,6 @@
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -167,7 +166,6 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
-            LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             @Nullable KeyguardLiftController keyguardLiftController,
@@ -248,7 +246,6 @@
                 assistManagerLazy,
                 configurationController,
                 notificationShadeWindowController,
-                lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
                 keyguardLiftController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 37d8c9a..781abe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import android.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 
@@ -32,4 +36,12 @@
         return notificationShadeWindowView.getNotificationPanelView();
     }
 
+    /** */
+    @Provides
+    @StatusBarComponent.StatusBarScope
+    @Nullable
+    public static LockIcon getLockIcon(
+            NotificationShadeWindowView notificationShadeWindowView) {
+        return notificationShadeWindowView.findViewById(R.id.lock_icon);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 2546813..f86b465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -295,8 +295,9 @@
         )
         controller.showDialog(context)
         exhaustExecutors()
-        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list).hasSize(2)
         assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+        assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
index 9762fff..eb5dd4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
@@ -72,7 +72,7 @@
         )
         dialog = PrivacyDialog(context, list, starter)
         dialog.show()
-        dialog.requireViewById<View>(R.id.link).callOnClick()
+        dialog.requireViewById<View>(R.id.privacy_item).callOnClick()
         verify(starter).invoke(PrivacyType.TYPE_MICROPHONE.permGroupName)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 53ff957..fe2f5f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -146,7 +146,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically demoted to Silent by the system. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
@@ -157,7 +158,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically ranked higher in your shade. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
@@ -168,7 +170,7 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically promoted to Default by the system. "
-                        + "Was this correct?",
+                        + "Let the developer know your feedback. Was this correct?",
                 prompt.getText().toString());
     }
 
@@ -180,7 +182,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically ranked lower in your shade. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 23c0930..bdde822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
     @Mock private NotificationPanelViewController mNotificationPanel;
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
-    @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private AuthController mAuthController;
 
     @Before
@@ -100,7 +99,7 @@
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
+                mAuthController, mNotificationIconAreaController);
 
         mDozeServiceHost.initialize(
                 mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 95a3505..60af16a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -30,12 +29,12 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,6 +44,7 @@
 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;
 
@@ -64,7 +64,7 @@
     @Mock
     private KeyguardIndicationController mKeyguardIndicationController;
     @Mock
-    private LockIcon mLockIcon; // TODO: make this not a mock once inject is removed.
+    private LockIcon mLockIcon;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
@@ -81,34 +81,36 @@
     private Resources mResources;
     @Mock
     private HeadsUpManagerPhone mHeadsUpManagerPhone;
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
 
     private LockscreenLockIconController mLockIconController;
+
+    @Captor ArgumentCaptor<OnAttachStateChangeListener> mOnAttachStateChangeCaptor;
+    @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+
     private OnAttachStateChangeListener mOnAttachStateChangeListener;
+    private StatusBarStateController.StateListener mStatusBarStateListener;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(true);
         when(mLockIcon.getContext()).thenReturn(mContext);
-        mLockIconController = new LockscreenLockIconController(
+        mLockIconController = new LockscreenLockIconController(mLockIcon,
                 mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
                 mShadeController, mAccessibilityController, mKeyguardIndicationController,
                 mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
                 mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
-                mHeadsUpManagerPhone, mKeyguardSecurityModel);
+                mHeadsUpManagerPhone);
 
-        ArgumentCaptor<OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
-                ArgumentCaptor.forClass(OnAttachStateChangeListener.class);
+        when(mLockIcon.isAttachedToWindow()).thenReturn(true);
+        mLockIconController.init();
 
-        doNothing().when(mLockIcon)
-                .addOnAttachStateChangeListener(
-                        onAttachStateChangeListenerArgumentCaptor.capture());
-        mLockIconController.attach(mLockIcon);
-
-        mOnAttachStateChangeListener = onAttachStateChangeListenerArgumentCaptor.getValue();
+        verify(mLockIcon).addOnAttachStateChangeListener(
+                mOnAttachStateChangeCaptor.capture());
+        mOnAttachStateChangeListener = mOnAttachStateChangeCaptor.getValue();
+        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+        mStatusBarStateListener = mStateListenerCaptor.getValue();
     }
 
     @Test
@@ -133,23 +135,17 @@
 
     @Test
     public void testVisibility_Dozing() {
-        ArgumentCaptor<StatusBarStateController.StateListener> sBStateListenerCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-
-        mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
-        verify(mStatusBarStateController).addCallback(sBStateListenerCaptor.capture());
-
         when(mStatusBarStateController.isDozing()).thenReturn(true);
-        sBStateListenerCaptor.getValue().onDozingChanged(true);
+        mStatusBarStateListener.onDozingChanged(true);
 
         verify(mLockIcon).updateIconVisibility(false);
     }
 
     @Test
     public void testVisibility_doNotShowLockIcon() {
-        when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(false);
+        mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
-        mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
         verify(mLockIcon).setVisibility(View.GONE);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c781ba..cae488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -386,7 +386,6 @@
                 () -> mAssistManager,
                 configurationController,
                 mNotificationShadeWindowController,
-                mLockscreenLockIconController,
                 mDozeParameters,
                 mScrimController,
                 mKeyguardLiftController,
@@ -436,6 +435,7 @@
         // initialized automatically.
         mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
+        mStatusBar.mLockscreenLockIconController = mLockscreenLockIconController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index da1f5d3..f8b6383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -37,6 +37,7 @@
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -343,6 +344,8 @@
         setConnectivityCommon(networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
             if (isConnected) {
+                mNetworkCallback.onAvailable(mock(Network.class),
+                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
                 mNetworkCallback.onCapabilitiesChanged(
                         mock(Network.class), new NetworkCapabilities(mNetCapabilities));
             } else {
@@ -357,6 +360,8 @@
         setConnectivityCommon(networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
             if (isConnected) {
+                mNetworkCallback.onAvailable(mock(Network.class),
+                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
                 mNetworkCallback.onCapabilitiesChanged(
                         mock(Network.class), new NetworkCapabilities(mNetCapabilities));
             } else {
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ae024ff..5dd271c 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -260,6 +260,10 @@
     // Package: android
     NOTE_ADB_WIFI_ACTIVE = 62;
 
+    // Notify the user a carrier suggestion is available to get IMSI exemption.
+    // Package: android
+    NOTE_CARRIER_SUGGESTION_AVAILABLE = 63;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index 1970b7d..61591c2 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,7 @@
         ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.searchui-sources",
+        ":services.smartspace-sources",
         ":services.speech-sources",
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
@@ -77,6 +78,7 @@
         "services.profcollect",
         "services.restrictions",
         "services.searchui",
+        "services.smartspace",
         "services.speech",
         "services.startop",
         "services.systemcaptions",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4f0efb4..c091dfa 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -216,8 +216,6 @@
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.utils.PriorityDump;
 
-import com.google.android.collect.Lists;
-
 import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
@@ -1577,7 +1575,7 @@
     @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
-        final ArrayList<NetworkInfo> result = Lists.newArrayList();
+        final ArrayList<NetworkInfo> result = new ArrayList<>();
         for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
                 networkType++) {
             NetworkInfo info = getNetworkInfo(networkType);
@@ -1851,7 +1849,7 @@
         // This contains IMSI details, so make sure the caller is privileged.
         NetworkStack.checkNetworkStackPermission(mContext);
 
-        final ArrayList<NetworkState> result = Lists.newArrayList();
+        final ArrayList<NetworkState> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai != null) {
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 8b506ba..41903fc 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -372,10 +372,12 @@
      * even from a previous boot.
      */
     public void unregisterHealthObserver(PackageHealthObserver observer) {
-        synchronized (mLock) {
-            mAllObservers.remove(observer.getName());
-        }
-        syncState("unregistering observer: " + observer.getName());
+        mLongTaskHandler.post(() -> {
+            synchronized (mLock) {
+                mAllObservers.remove(observer.getName());
+            }
+            syncState("unregistering observer: " + observer.getName());
+        });
     }
 
     /**
@@ -982,7 +984,11 @@
                     if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
                         return;
                     }
-                    updateConfigs();
+                    try {
+                        updateConfigs();
+                    } catch (Exception ignore) {
+                        Slog.w(TAG, "Failed to reload device config changes");
+                    }
                 });
     }
 
@@ -990,7 +996,8 @@
      * Health check is enabled or disabled after reading the flags
      * from DeviceConfig.
      */
-    private void updateConfigs() {
+    @VisibleForTesting
+    void updateConfigs() {
         synchronized (mLock) {
             mTriggerFailureCount = DeviceConfig.getInt(
                     DeviceConfig.NAMESPACE_ROLLBACK,
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index a08d066..e96fd39 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -32,6 +32,7 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkStack;
 import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.net.TestNetworkInterface;
@@ -48,6 +49,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetdUtils;
 
 import java.io.UncheckedIOException;
 import java.net.Inet4Address;
@@ -317,10 +319,10 @@
         }
 
         try {
-            // This requires NETWORK_STACK privileges.
             final long token = Binder.clearCallingIdentity();
             try {
-                mNMS.setInterfaceUp(iface);
+                NetworkStack.checkNetworkStackPermission(mContext);
+                NetdUtils.setInterfaceUp(mNetd, iface);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 8562b0d..6a72010 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -27,10 +27,12 @@
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -291,6 +293,12 @@
                 @NonNull VcnConfig config) {
             return new Vcn(vcnContext, subscriptionGroup, config);
         }
+
+        /** Gets the subId indicated by the given {@link WifiInfo}. */
+        public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
+            // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -582,8 +590,36 @@
                 "Must have permission NETWORK_FACTORY or be the SystemServer to get underlying"
                         + " Network policies");
 
-        // TODO(b/175914059): implement policy generation once VcnManagementService is able to
-        // determine policies
+        // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates
+        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+            TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                    (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier();
+            subId = telephonyNetworkSpecifier.getSubscriptionId();
+        } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+                && networkCapabilities.getTransportInfo() instanceof WifiInfo) {
+            WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+            subId = mDeps.getSubIdForWifiInfo(wifiInfo);
+        }
+
+        boolean isVcnManagedNetwork = false;
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            synchronized (mLock) {
+                ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
+
+                // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode
+                if (mVcns.containsKey(subGroup)) {
+                    isVcnManagedNetwork = true;
+                }
+            }
+        }
+        if (isVcnManagedNetwork) {
+            networkCapabilities.removeCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
 
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index eca1dfa..6a816af 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -22,12 +22,20 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.vibrator.IVibrator;
+import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.CombinedVibrationEffect;
+import android.os.ExternalVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IExternalVibratorService;
 import android.os.IVibratorManagerService;
+import android.os.IVibratorStateListener;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.Trace;
@@ -37,12 +45,17 @@
 import android.os.VibratorInfo;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.DumpUtils;
+import com.android.server.vibrator.InputDeviceDelegate;
 import com.android.server.vibrator.Vibration;
 import com.android.server.vibrator.VibrationScaler;
 import com.android.server.vibrator.VibrationSettings;
+import com.android.server.vibrator.VibrationThread;
 import com.android.server.vibrator.VibratorController;
 
 import libcore.util.NativeAllocationRegistry;
@@ -50,7 +63,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -83,18 +100,32 @@
         }
     }
 
+    // Used to generate globally unique vibration ids.
+    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
     private final Object mLock = new Object();
     private final Context mContext;
+    private final PowerManager.WakeLock mWakeLock;
+    private final IBatteryStats mBatteryStatsService;
     private final Handler mHandler;
     private final AppOpsManager mAppOps;
     private final NativeWrapper mNativeWrapper;
+    private final VibratorManagerRecords mVibratorManagerRecords;
     private final int[] mVibratorIds;
     private final SparseArray<VibratorController> mVibrators;
+    private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
     @GuardedBy("mLock")
     private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
+    @GuardedBy("mLock")
+    private VibrationThread mCurrentVibration;
+    @GuardedBy("mLock")
+    private VibrationThread mNextVibration;
+    @GuardedBy("mLock")
+    private ExternalVibrationHolder mCurrentExternalVibration;
 
     private VibrationSettings mVibrationSettings;
     private VibrationScaler mVibrationScaler;
+    private InputDeviceDelegate mInputDeviceDelegate;
 
     static native long nativeInit();
 
@@ -109,8 +140,19 @@
         mNativeWrapper = injector.getNativeWrapper();
         mNativeWrapper.init();
 
+        int dumpLimit = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
+        mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+
+        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                BatteryStats.SERVICE_NAME));
+
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+        mWakeLock.setReferenceCounted(true);
+
         int[] vibratorIds = mNativeWrapper.getVibratorIds();
         if (vibratorIds == null) {
             mVibratorIds = new int[0];
@@ -134,6 +176,7 @@
         try {
             mVibrationSettings = new VibrationSettings(mContext, mHandler);
             mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
 
             mVibrationSettings.addListener(this::updateServiceState);
 
@@ -145,6 +188,11 @@
     }
 
     @Override // Binder call
+    public int[] getVibratorIds() {
+        return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+    }
+
+    @Override // Binder call
     @Nullable
     public VibratorInfo getVibratorInfo(int vibratorId) {
         VibratorController controller = mVibrators.get(vibratorId);
@@ -152,8 +200,37 @@
     }
 
     @Override // Binder call
-    public int[] getVibratorIds() {
-        return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+    public boolean isVibrating(int vibratorId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "isVibrating");
+        VibratorController controller = mVibrators.get(vibratorId);
+        return controller != null && controller.isVibrating();
+    }
+
+    @Override // Binder call
+    public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "registerVibratorStateListener");
+        VibratorController controller = mVibrators.get(vibratorId);
+        if (controller == null) {
+            return false;
+        }
+        return controller.registerVibratorStateListener(listener);
+    }
+
+    @Override // Binder call
+    public boolean unregisterVibratorStateListener(int vibratorId,
+            IVibratorStateListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "unregisterVibratorStateListener");
+        VibratorController controller = mVibrators.get(vibratorId);
+        if (controller == null) {
+            return false;
+        }
+        return controller.unregisterVibratorStateListener(listener);
     }
 
     @Override // Binder call
@@ -161,9 +238,10 @@
             @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
         try {
-            if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
-                throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
-            }
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE_ALWAYS_ON,
+                    "setAlwaysOnEffect");
+
             if (effect == null) {
                 synchronized (mLock) {
                     mAlwaysOnEffects.delete(alwaysOnId);
@@ -200,12 +278,113 @@
     @Override // Binder call
     public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        throw new UnsupportedOperationException("Not implemented");
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
+        try {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
+
+            if (token == null) {
+                Slog.e(TAG, "token must not be null");
+                return;
+            }
+            enforceUpdateAppOpsStatsPermission(uid);
+            if (!isEffectValid(effect)) {
+                return;
+            }
+            effect = fixupVibrationEffect(effect);
+            attrs = fixupVibrationAttributes(attrs);
+            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
+                    uid, opPkg, reason);
+
+            synchronized (mLock) {
+                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
+                if (ignoreStatus != null) {
+                    endVibrationLocked(vib, ignoreStatus);
+                    return;
+                }
+
+                VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+                        mBatteryStatsService, mVibrationCallbacks);
+
+                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+                if (ignoreStatus != null) {
+                    endVibrationLocked(vib, ignoreStatus);
+                    return;
+                }
+
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    if (mCurrentVibration != null) {
+                        mCurrentVibration.cancel();
+                    }
+                    Vibration.Status status = startVibrationLocked(vibThread);
+                    if (status != Vibration.Status.RUNNING) {
+                        endVibrationLocked(vib, status);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
     }
 
     @Override // Binder call
     public void cancelVibrate(IBinder token) {
-        throw new UnsupportedOperationException("Not implemented");
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE,
+                    "cancelVibrate");
+
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration.");
+                }
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mNextVibration = null;
+                    if (mCurrentVibration != null
+                            && mCurrentVibration.getVibration().token == token) {
+                        mCurrentVibration.cancel();
+                    }
+                    if (mCurrentExternalVibration != null) {
+                        mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
+                        mVibratorManagerRecords.record(mCurrentExternalVibration);
+                        mCurrentExternalVibration.externalVibration.mute();
+                        mCurrentExternalVibration = null;
+                        // TODO(b/167946816): set external control to false
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        final long ident = Binder.clearCallingIdentity();
+
+        boolean isDumpProto = false;
+        for (String arg : args) {
+            if (arg.equals("--proto")) {
+                isDumpProto = true;
+            }
+        }
+        try {
+            if (isDumpProto) {
+                mVibratorManagerRecords.dumpProto(fd);
+            } else {
+                mVibratorManagerRecords.dumpText(pw);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     @Override
@@ -214,11 +393,24 @@
         new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
     }
 
-    private void updateServiceState() {
+    @VisibleForTesting
+    void updateServiceState() {
         synchronized (mLock) {
+            boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
+                    mVibrationSettings.shouldVibrateInputDevices());
+
             for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
                 updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
             }
+
+            if (mCurrentVibration == null) {
+                return;
+            }
+
+            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
+                    mCurrentVibration.getVibration().attrs.getUsage())) {
+                mCurrentVibration.cancel();
+            }
         }
     }
 
@@ -230,9 +422,9 @@
             if (vibrator == null) {
                 continue;
             }
-            Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked(
+            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
                     vib.uid, vib.opPkg, vib.attrs);
-            if (ignoredStatus == null) {
+            if (ignoreStatus == null) {
                 effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
             } else {
                 // Vibration should not run, use null effect to remove registered effect.
@@ -242,6 +434,135 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+        try {
+            Vibration vib = vibThread.getVibration();
+            vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+
+            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
+                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
+
+            if (inputDevicesAvailable) {
+                return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
+            }
+
+            if (mCurrentVibration == null) {
+                return startVibrationThreadLocked(vibThread);
+            }
+
+            mNextVibration = vibThread;
+            return Vibration.Status.RUNNING;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
+        try {
+            Vibration vib = vibThread.getVibration();
+            int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
+            switch (mode) {
+                case AppOpsManager.MODE_ALLOWED:
+                    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                    mCurrentVibration = vibThread;
+                    mCurrentVibration.start();
+                    return Vibration.Status.RUNNING;
+                case AppOpsManager.MODE_ERRORED:
+                    Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
+                    return Vibration.Status.IGNORED_ERROR_APP_OPS;
+                default:
+                    return Vibration.Status.IGNORED_APP_OPS;
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
+        vib.end(status);
+        mVibratorManagerRecords.record(vib);
+    }
+
+    @GuardedBy("mLock")
+    private void reportFinishedVibrationLocked(Vibration.Status status) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+        try {
+            Vibration vib = mCurrentVibration.getVibration();
+            mCurrentVibration = null;
+            endVibrationLocked(vib, status);
+            finishAppOpModeLocked(vib.uid, vib.opPkg);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private void onVibrationComplete(int vibratorId, long vibrationId) {
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+                            + " complete, notifying thread");
+                }
+                mCurrentVibration.vibratorComplete(vibratorId);
+            }
+        }
+    }
+
+    /**
+     * Check if given vibration should be ignored in favour of one of the vibrations currently
+     * running on the same vibrators.
+     *
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
+        if (vibThread.getVibration().isRepeating()) {
+            // Repeating vibrations always take precedence.
+            return null;
+        }
+        if (mCurrentVibration != null && mCurrentVibration.getVibration().isRepeating()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring incoming vibration in favor of previous alarm vibration");
+            }
+            return Vibration.Status.IGNORED_FOR_ALARM;
+        }
+        return null;
+    }
+
+    /**
+     * 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.
@@ -271,7 +592,7 @@
             return Vibration.Status.IGNORED_RINGTONE;
         }
 
-        int mode = getAppOpMode(uid, opPkg, attrs);
+        int mode = checkAppOpModeLocked(uid, opPkg, attrs);
         if (mode != AppOpsManager.MODE_ALLOWED) {
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
@@ -290,21 +611,49 @@
      * Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and
      * {@code attrs}. This will return one of the AppOpsManager.MODE_*.
      */
-    private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) {
+    @GuardedBy("mLock")
+    private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
                 attrs.getAudioUsage(), uid, opPkg);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg);
-        }
-        if (mode == AppOpsManager.MODE_IGNORED
-                && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+        int fixedMode = fixupAppOpModeLocked(mode, attrs);
+        if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
             // If we're just ignoring the vibration op then this is set by DND and we should ignore
             // if we're asked to bypass. AppOps won't be able to record this operation, so make
             // sure we at least note it in the logs for debugging.
             Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
-            mode = AppOpsManager.MODE_ALLOWED;
         }
-        return mode;
+        return fixedMode;
+    }
+
+    /** Start an operation in {@link AppOpsManager}, if allowed. */
+    @GuardedBy("mLock")
+    private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+        return fixupAppOpModeLocked(
+                mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
+    }
+
+    /**
+     * Finish a previously started operation in {@link AppOpsManager}. This will be a noop if no
+     * operation with same uid was previously started.
+     */
+    @GuardedBy("mLock")
+    private void finishAppOpModeLocked(int uid, String opPkg) {
+        mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
+    }
+
+    /**
+     * Enforces {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} to incoming UID if it's
+     * different from the calling UID.
+     */
+    private void enforceUpdateAppOpsStatsPermission(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
     /**
@@ -330,6 +679,49 @@
     }
 
     /**
+     * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
+     * VibrationSettings#getFallbackEffect}.
+     */
+    private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+        if (effect instanceof CombinedVibrationEffect.Mono) {
+            return CombinedVibrationEffect.createSynced(
+                    fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+        } else if (effect instanceof CombinedVibrationEffect.Stereo) {
+            CombinedVibrationEffect.SyncedCombination combination =
+                    CombinedVibrationEffect.startSynced();
+            SparseArray<VibrationEffect> effects =
+                    ((CombinedVibrationEffect.Stereo) effect).getEffects();
+            for (int i = 0; i < effects.size(); i++) {
+                combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+            }
+            return combination.combine();
+        } else if (effect instanceof CombinedVibrationEffect.Sequential) {
+            CombinedVibrationEffect.SequentialCombination combination =
+                    CombinedVibrationEffect.startSequential();
+            List<CombinedVibrationEffect> effects =
+                    ((CombinedVibrationEffect.Sequential) effect).getEffects();
+            for (CombinedVibrationEffect e : effects) {
+                combination.addNext(fixupVibrationEffect(e));
+            }
+            return combination.combine();
+        }
+        return effect;
+    }
+
+    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
+        if (effect instanceof VibrationEffect.Prebaked
+                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
+            if (fallback != null) {
+                return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
+                        fallback);
+            }
+        }
+        return effect;
+    }
+
+    /**
      * Return new {@link VibrationAttributes} that only applies flags that this user has permissions
      * to use.
      */
@@ -388,6 +780,19 @@
         }
     }
 
+    /**
+     * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
+     * allow bypassing {@link AppOpsManager} checks.
+     */
+    @GuardedBy("mLock")
+    private int fixupAppOpModeLocked(int mode, VibrationAttributes attrs) {
+        if (mode == AppOpsManager.MODE_IGNORED
+                && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+            return AppOpsManager.MODE_ALLOWED;
+        }
+        return mode;
+    }
+
     private boolean hasPermission(String permission) {
         return mContext.checkCallingOrSelfPermission(permission)
                 == PackageManager.PERMISSION_GRANTED;
@@ -428,6 +833,42 @@
     }
 
     /**
+     * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
+     * and reports them when finished.
+     */
+    private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
+
+        @Override
+        public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
+            // TODO(b/167946816): call IVibratorManager to prepare
+        }
+
+        @Override
+        public void triggerSyncedVibration(long vibrationId) {
+            // TODO(b/167946816): call IVibratorManager to trigger
+        }
+
+        @Override
+        public void onVibrationEnded(long vibrationId, Vibration.Status status) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status);
+            }
+            synchronized (mLock) {
+                if (mCurrentVibration != null
+                        && mCurrentVibration.getVibration().id == vibrationId) {
+                    reportFinishedVibrationLocked(status);
+
+                    if (mNextVibration != null) {
+                        VibrationThread vibThread = mNextVibration;
+                        mNextVibration = null;
+                        startVibrationThreadLocked(vibThread);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      * Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
      * reference to this service.
      */
@@ -443,7 +884,7 @@
         public void onComplete(int vibratorId, long vibrationId) {
             VibratorManagerService service = mServiceRef.get();
             if (service != null) {
-                // TODO(b/159207608): finish vibration if all vibrators finished for this vibration
+                service.onVibrationComplete(vibratorId, vibrationId);
             }
         }
     }
@@ -469,6 +910,41 @@
         }
     }
 
+    /** Holder for a {@link ExternalVibration}. */
+    private final class ExternalVibrationHolder {
+
+        public final ExternalVibration externalVibration;
+        public int scale;
+
+        private final long mStartTimeDebug;
+        private long mEndTimeDebug;
+        private Vibration.Status mStatus;
+
+        private ExternalVibrationHolder(ExternalVibration externalVibration) {
+            this.externalVibration = externalVibration;
+            this.scale = IExternalVibratorService.SCALE_NONE;
+            mStartTimeDebug = System.currentTimeMillis();
+            mStatus = Vibration.Status.RUNNING;
+        }
+
+        public void end(Vibration.Status status) {
+            if (mStatus != Vibration.Status.RUNNING) {
+                // Vibration already ended, keep first ending status set and ignore this one.
+                return;
+            }
+            mStatus = status;
+            mEndTimeDebug = System.currentTimeMillis();
+        }
+
+        public Vibration.DebugInfo getDebugInfo() {
+            return new Vibration.DebugInfo(
+                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+                    scale, externalVibration.getVibrationAttributes(),
+                    externalVibration.getUid(), externalVibration.getPackage(),
+                    /* reason= */ null, mStatus);
+        }
+    }
+
     /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
     @VisibleForTesting
     public static class NativeWrapper {
@@ -494,8 +970,158 @@
         }
     }
 
+    /** Keep records of vibrations played and provide debug information for this service. */
+    private final class VibratorManagerRecords {
+        @GuardedBy("mLock")
+        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) {
+            mPreviousVibrationsLimit = limit;
+        }
+
+        @GuardedBy("mLock")
+        void record(Vibration vib) {
+            int usage = vib.attrs.getUsage();
+            if (!mPreviousVibrations.contains(usage)) {
+                mPreviousVibrations.put(usage, new LinkedList<>());
+            }
+            record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+        }
+
+        @GuardedBy("mLock")
+        void record(ExternalVibrationHolder vib) {
+            record(mPreviousExternalVibrations, vib.getDebugInfo());
+        }
+
+        @GuardedBy("mLock")
+        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) {
+                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("  mNextVibration:");
+                pw.println("    " + mNextVibration == null
+                        ? null : mNextVibration.getVibration().getDebugInfo());
+                pw.println("  mCurrentExternalVibration:");
+                pw.println("    " + mCurrentExternalVibration == null
+                        ? null : mCurrentExternalVibration.getDebugInfo());
+                pw.println();
+                pw.println("  mVibrationSettings=" + mVibrationSettings);
+                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("  Previous external vibrations:");
+                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                    pw.println("    " + info);
+                }
+            }
+        }
+
+        synchronized void dumpProto(FileDescriptor fd) {
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+            synchronized (mLock) {
+                mVibrationSettings.dumpProto(proto);
+                if (mCurrentVibration != null) {
+                    mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+                            VibratorServiceDumpProto.CURRENT_VIBRATION);
+                }
+                if (mCurrentExternalVibration != null) {
+                    mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+                            VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+                }
+
+                boolean isVibrating = false;
+                boolean isUnderExternalControl = false;
+                for (int i = 0; i < mVibrators.size(); i++) {
+                    isVibrating |= mVibrators.valueAt(i).isVibrating();
+                    isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+                }
+                proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
+                proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+                        isUnderExternalControl);
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_RINGTONE)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_NOTIFICATION)) {
+                    info.dumpProto(proto,
+                            VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_ALARM)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_UNKNOWN)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                }
+            }
+            proto.flush();
+        }
+    }
+
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
+        public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+
+        private final class CommonOptions {
+            public boolean force = false;
+            public String description = "Shell command";
+
+            CommonOptions() {
+                String nextArg;
+                while ((nextArg = peekNextArg()) != null) {
+                    switch (nextArg) {
+                        case "-f":
+                            getNextArgRequired(); // consume the -f argument;
+                            force = true;
+                            break;
+                        case "-d":
+                            getNextArgRequired(); // consume the -d argument;
+                            description = getNextArgRequired();
+                            break;
+                        default:
+                            // Not a common option, finish reading.
+                            return;
+                    }
+                }
+            }
+        }
 
         private final IBinder mToken;
 
@@ -505,10 +1131,27 @@
 
         @Override
         public int onCommand(String cmd) {
-            if ("list".equals(cmd)) {
-                return runListVibrators();
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
+            try {
+                if ("list".equals(cmd)) {
+                    return runListVibrators();
+                }
+                if ("synced".equals(cmd)) {
+                    return runMono();
+                }
+                if ("combined".equals(cmd)) {
+                    return runStereo();
+                }
+                if ("sequential".equals(cmd)) {
+                    return runSequential();
+                }
+                if ("cancel".equals(cmd)) {
+                    return runCancel();
+                }
+                return handleDefaultCommands(cmd);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
-            return handleDefaultCommands(cmd);
         }
 
         private int runListVibrators() {
@@ -525,6 +1168,157 @@
             }
         }
 
+        private int runMono() {
+            CommonOptions commonOptions = new CommonOptions();
+            VibrationEffect effect = nextEffect();
+            if (effect == null) {
+                return 0;
+            }
+
+            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runStereo() {
+            CommonOptions commonOptions = new CommonOptions();
+            CombinedVibrationEffect.SyncedCombination combination =
+                    CombinedVibrationEffect.startSynced();
+            while ("-v".equals(getNextOption())) {
+                int vibratorId = Integer.parseInt(getNextArgRequired());
+                VibrationEffect effect = nextEffect();
+                if (effect != null) {
+                    combination.addVibrator(vibratorId, effect);
+                }
+            }
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runSequential() {
+            CommonOptions commonOptions = new CommonOptions();
+
+            CombinedVibrationEffect.SequentialCombination combination =
+                    CombinedVibrationEffect.startSequential();
+            while ("-v".equals(getNextOption())) {
+                int vibratorId = Integer.parseInt(getNextArgRequired());
+                int delay = 0;
+                if ("-w".equals(getNextOption())) {
+                    delay = Integer.parseInt(getNextArgRequired());
+                }
+                VibrationEffect effect = nextEffect();
+                if (effect != null) {
+                    combination.addNext(vibratorId, effect, delay);
+                }
+            }
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runCancel() {
+            cancelVibrate(mToken);
+            return 0;
+        }
+
+        @Nullable
+        private VibrationEffect nextEffect() {
+            String effectType = getNextArgRequired();
+            if ("oneshot".equals(effectType)) {
+                return nextOneShot();
+            }
+            if ("waveform".equals(effectType)) {
+                return nextWaveform();
+            }
+            if ("prebaked".equals(effectType)) {
+                return nextPrebaked();
+            }
+            if ("composed".equals(effectType)) {
+                return nextComposed();
+            }
+            return null;
+        }
+
+        private VibrationEffect nextOneShot() {
+            boolean hasAmplitude = "-a".equals(getNextOption());
+            long duration = Long.parseLong(getNextArgRequired());
+            int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
+                    : VibrationEffect.DEFAULT_AMPLITUDE;
+            return VibrationEffect.createOneShot(duration, amplitude);
+        }
+
+        private VibrationEffect nextWaveform() {
+            boolean hasAmplitudes = false;
+            int repeat = -1;
+
+            String nextOption = getNextOption();
+            while (nextOption != null) {
+                if ("-a".equals(nextOption)) {
+                    hasAmplitudes = true;
+                } else if ("-r".equals(nextOption)) {
+                    repeat = Integer.parseInt(getNextArgRequired());
+                }
+                nextOption = getNextOption();
+            }
+            List<Long> durations = new ArrayList<>();
+            List<Integer> amplitudes = new ArrayList<>();
+
+            String nextArg;
+            while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) {
+                durations.add(Long.parseLong(getNextArgRequired()));
+                if (hasAmplitudes) {
+                    amplitudes.add(Integer.parseInt(getNextArgRequired()));
+                }
+            }
+
+            long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray();
+            if (!hasAmplitudes) {
+                return VibrationEffect.createWaveform(durationArray, repeat);
+            }
+
+            int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
+            return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
+        }
+
+        private VibrationEffect nextPrebaked() {
+            boolean shouldFallback = "-b".equals(getNextOption());
+            int effectId = Integer.parseInt(getNextArgRequired());
+            return VibrationEffect.get(effectId, shouldFallback);
+        }
+
+        private VibrationEffect nextComposed() {
+            VibrationEffect.Composition composition = VibrationEffect.startComposition();
+            String nextArg;
+            while ((nextArg = peekNextArg()) != null) {
+                int delay = 0;
+                if ("-w".equals(nextArg)) {
+                    getNextArgRequired(); // consume the -w option
+                    delay = Integer.parseInt(getNextArgRequired());
+                } else if ("-v".equals(nextArg)) {
+                    // Starting next vibrator, this composed effect if finished.
+                    break;
+                }
+                int primitiveId = Integer.parseInt(getNextArgRequired());
+                composition.addPrimitive(primitiveId, /* scale= */ 1f, delay);
+            }
+            return composition.compose();
+        }
+
+        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
+            final int flags =
+                    commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
+            return new VibrationAttributes.Builder()
+                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+                    // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
+                    .setUsage(VibrationAttributes.USAGE_TOUCH)
+                    .build();
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
@@ -535,6 +1329,49 @@
                 pw.println("  list");
                 pw.println("    Prints the id of device vibrators. This does not include any ");
                 pw.println("    connected input device.");
+                pw.println("  synced [options] <effect>");
+                pw.println("    Vibrates effect on all vibrators in sync.");
+                pw.println("  combined [options] (-v <vibrator-id> <effect>)...");
+                pw.println("    Vibrates different effects on each vibrator in sync.");
+                pw.println("  sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)...");
+                pw.println("    Vibrates different effects on each vibrator in sequence.");
+                pw.println("  cancel");
+                pw.println("    Cancels any active vibration");
+                pw.println("");
+                pw.println("Effect commands:");
+                pw.println("  oneshot [-a] <duration> [<amplitude>]");
+                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
+                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale amplitude.");
+                pw.println("    If -a is provided, the command accepts a second argument for ");
+                pw.println("    amplitude, in a scale of 1-255.");
+                pw.println("  waveform [-r <index>] [-a] (<duration> [<amplitude>])...");
+                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
+                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
+                pw.println("    user setting will be used to scale amplitude.");
+                pw.println("    If -r is provided, the waveform loops back to the specified");
+                pw.println("    index (e.g. 0 loops from the beginning)");
+                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
+                pw.println("    otherwise, it accepts durations only and alternates off/on");
+                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
+                pw.println("  prebaked [-b] <effect-id>");
+                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
+                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale amplitude.");
+                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
+                pw.println("    the device doesn't support the given effect-id.");
+                pw.println("  composed [-w <delay>] <primitive-id>...");
+                pw.println("    Vibrates with a composed effect; ignored when device is on DND ");
+                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale primitive intensities.");
+                pw.println("    If -w is provided, the next primitive will be played after the ");
+                pw.println("    specified wait time in milliseconds.");
+                pw.println("");
+                pw.println("Common Options:");
+                pw.println("  -f");
+                pw.println("    Force. Ignore Do Not Disturb setting.");
+                pw.println("  -d <description>");
+                pw.println("    Add description to the vibration.");
                 pw.println("");
             }
         }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 97e313e..026eb63 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -772,18 +772,7 @@
             proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
             proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
                     mVibratorController.isUnderExternalControl());
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
-            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+            mVibrationSettings.dumpProto(proto);
 
             for (Vibration.DebugInfo info : mPreviousRingVibrations) {
                 info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index d63a6c3..5b50431 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -111,7 +111,6 @@
     };
 
     public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
-            "android.hardware.audio@2.0::IDevicesFactory",
             "android.hardware.audio@4.0::IDevicesFactory",
             "android.hardware.audio@5.0::IDevicesFactory",
             "android.hardware.audio@6.0::IDevicesFactory",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0c8172d..6216fc0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2815,12 +2815,24 @@
             r = smap.mServicesByIntent.get(filter);
             if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
         }
-        if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
-                && !callingPackage.equals(r.packageName)) {
-            // If an external service is running within its own package, other packages
-            // should not bind to that instance.
-            r = null;
-            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+        if (r != null) {
+            // Compared to resolveService below, the ServiceRecord here is retrieved from
+            // ServiceMap so the package visibility doesn't apply to it. We need to filter it.
+            if (mAm.getPackageManagerInternal().filterAppAccess(r.packageName, callingUid,
+                    userId)) {
+                Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId
+                        + ": not found");
+                return null;
+            }
+            if ((r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
+                    && !callingPackage.equals(r.packageName)) {
+                // If an external service is running within its own package, other packages
+                // should not bind to that instance.
+                r = null;
+                if (DEBUG_SERVICE) {
+                    Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+                }
+            }
         }
         if (r == null) {
             try {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b153cfd..170c34d 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -43,6 +43,7 @@
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
@@ -96,8 +97,6 @@
                         return t;
                     });
 
-    private final Context mContext;
-
     @GuardedBy("mStats")
     private final BatteryStatsImpl mStats;
 
@@ -168,15 +167,39 @@
     @GuardedBy("this")
     private long mLastCollectionTimeStamp;
 
+    final Injector mInjector;
+
+    @VisibleForTesting
+    public static class Injector {
+        private final Context mContext;
+
+        Injector(Context context) {
+            mContext = context;
+        }
+
+        public <T> T getSystemService(Class<T> serviceClass) {
+            return mContext.getSystemService(serviceClass);
+        }
+
+        public <T> T getLocalService(Class<T> serviceClass) {
+            return LocalServices.getService(serviceClass);
+        }
+    }
+
     BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
-        mContext = context;
+        this(new Injector(context), stats);
+    }
+
+    @VisibleForTesting
+    BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+        mInjector = injector;
         mStats = stats;
     }
 
     public void systemServicesReady() {
-        final WifiManager wm = mContext.getSystemService(WifiManager.class);
-        final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        final PowerStatsInternal psi = LocalServices.getService(PowerStatsInternal.class);
+        final WifiManager wm = mInjector.getSystemService(WifiManager.class);
+        final TelephonyManager tm = mInjector.getSystemService(TelephonyManager.class);
+        final PowerStatsInternal psi = mInjector.getLocalService(PowerStatsInternal.class);
         synchronized (mWorkerLock) {
             mWifiManager = wm;
             mTelephony = tm;
@@ -747,7 +770,8 @@
      * EnergyConsumerResult}[]
      */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
+    @VisibleForTesting
+    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
         final EnergyConsumerResult[] results;
         try {
             results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
@@ -761,28 +785,41 @@
         final int[] subsystems = new int[size];
         final long[] energyUJ = new long[size];
 
+        int count = 0;
         for (int i = 0; i < size; i++) {
             final EnergyConsumerResult consumer = results[i];
             final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
                     MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
             if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[i] = subsystem;
-            energyUJ[i] = consumer.energyUWs;
+            subsystems[count] = subsystem;
+            energyUJ[count] = consumer.energyUWs;
+            count++;
         }
+        final int arraySize = count;
         return new MeasuredEnergyArray() {
             @Override
             public int getSubsystem(int index) {
+                if (index >= size()) {
+                    throw new IllegalArgumentException(
+                            "Out of bounds subsystem index! index : " + index + ", size : "
+                                    + size());
+                }
                 return subsystems[index];
             }
 
             @Override
             public long getEnergy(int index) {
+                if (index >= size()) {
+                    throw new IllegalArgumentException(
+                            "Out of bounds subsystem index! index : " + index + ", size : "
+                                    + size());
+                }
                 return energyUJ[index];
             }
 
             @Override
             public int size() {
-                return size;
+                return arraySize;
             }
         };
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a768532..bbf927b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4447,7 +4447,7 @@
             }
 
             app.getPkgList().forEachPackage(packageName -> {
-                if (updateFrameworkRes && packagesToUpdate.contains(packageName)) {
+                if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                     try {
                         final ApplicationInfo ai = AppGlobals.getPackageManager()
                                 .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index cf309f4..9fd2bd7 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -623,6 +623,8 @@
             DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
             pw.print(" lastRss=");
             DebugUtils.printSizeValue(pw, mLastRss * 1024);
+            pw.println();
+            pw.print(prefix);
             pw.print(" trimMemoryLevel=");
             pw.println(mTrimMemoryLevel);
             pw.println();
diff --git a/services/core/java/com/android/server/graphics/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
similarity index 97%
rename from services/core/java/com/android/server/graphics/GameManagerService.java
rename to services/core/java/com/android/server/app/GameManagerService.java
index 876f02f..3acad49 100644
--- a/services/core/java/com/android/server/graphics/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.graphics;
+package com.android.server.app;
 
 import android.annotation.NonNull;
+import android.app.GameManager;
+import android.app.GameManager.GameMode;
+import android.app.IGameManagerService;
 import android.content.Context;
-import android.graphics.GameManager;
-import android.graphics.GameManager.GameMode;
-import android.graphics.IGameManagerService;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
diff --git a/services/core/java/com/android/server/graphics/Settings.java b/services/core/java/com/android/server/app/Settings.java
similarity index 98%
rename from services/core/java/com/android/server/graphics/Settings.java
rename to services/core/java/com/android/server/app/Settings.java
index bbd84d0..ab367fb 100644
--- a/services/core/java/com/android/server/graphics/Settings.java
+++ b/services/core/java/com/android/server/app/Settings.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.graphics;
+package com.android.server.app;
 
-import android.graphics.GameManager;
+import android.app.GameManager;
 import android.os.FileUtils;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b282484..1a4f20c7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -57,6 +57,7 @@
 import com.android.server.ConnectivityService;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -1025,6 +1026,8 @@
                 + (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "")
                 + (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "")
                 + (clatd.isStarted() ? " clat{" + clatd + "} " : "")
+                + (declaredUnderlyingNetworks != null
+                        ? " underlying{" + Arrays.toString(declaredUnderlyingNetworks) + "}" : "")
                 + "  lp{" + linkProperties + "}"
                 + "  nc{" + networkCapabilities + "}"
                 + "}";
diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
new file mode 100644
index 0000000..b082b25
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
@@ -0,0 +1,92 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A class to detect font-related native crash.
+ *
+ * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected,
+ * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them,
+ * we use a marker file to detect crash.
+ * <ol>
+ *     <li>Create a marker file before reading fs-verity protected font files.
+ *     <li>Delete the marker file after reading font files successfully.
+ *     <li>If the marker file is found in the next process startup, it means that the process
+ *         crashed before. We will delete font files to prevent crash loop.
+ * </ol>
+ *
+ * <p>Example usage:
+ * <pre>
+ *     FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file"));
+ *     if (detector.hasCrashed()) {
+ *         // Do cleanup
+ *     }
+ *     try (FontCrashDetector.MonitoredBlock b = detector.start()) {
+ *         // Read files
+ *     }
+ * </pre>
+ *
+ * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring
+ * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight.
+ * Please use this class sparingly with caution.
+ */
+/* package */ final class FontCrashDetector {
+
+    private static final String TAG = "FontCrashDetector";
+
+    @NonNull
+    private final File mMarkerFile;
+
+    /* package */ FontCrashDetector(@NonNull File markerFile) {
+        mMarkerFile = markerFile;
+    }
+
+    /* package */ boolean hasCrashed() {
+        return mMarkerFile.exists();
+    }
+
+    /* package */ void clear() {
+        if (!mMarkerFile.delete()) {
+            Slog.e(TAG, "Could not delete marker file: " + mMarkerFile);
+        }
+    }
+
+    /** Starts crash monitoring. */
+    /* package */ MonitoredBlock start() {
+        try {
+            mMarkerFile.createNewFile();
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e);
+        }
+        return new MonitoredBlock();
+    }
+
+    /** A helper class to monitor crash with try-with-resources syntax. */
+    /* package */ class MonitoredBlock implements AutoCloseable {
+        /** Ends crash monitoring. */
+        @Override
+        public void close() {
+            clear();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7461405..8e5215b 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -64,6 +64,7 @@
     private static final String TAG = "FontManagerService";
 
     private static final String FONT_FILES_DIR = "/data/fonts/files";
+    private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt";
 
     @Override
     public FontConfig getFontConfig() throws RemoteException {
@@ -179,6 +180,13 @@
         }
     }
 
+    @NonNull
+    private final Context mContext;
+
+    @GuardedBy("FontManagerService.this")
+    @NonNull
+    private final FontCrashDetector mFontCrashDetector;
+
     @Nullable
     private final UpdatableFontDir mUpdatableFontDir;
 
@@ -188,7 +196,9 @@
 
     private FontManagerService(Context context) {
         mContext = context;
+        mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE));
         mUpdatableFontDir = createUpdatableFontDir();
+        initialize();
     }
 
     @Nullable
@@ -201,20 +211,35 @@
                 new OtfFontFileParser(), new FsverityUtilImpl());
     }
 
-
-    @NonNull
-    private final Context mContext;
+    private void initialize() {
+        synchronized (FontManagerService.this) {
+            if (mUpdatableFontDir == null) {
+                mSerializedFontMap = buildNewSerializedFontMap();
+                return;
+            }
+            if (mFontCrashDetector.hasCrashed()) {
+                Slog.i(TAG, "Crash detected. Clearing font updates.");
+                try {
+                    mUpdatableFontDir.clearUpdates();
+                } catch (SystemFontException e) {
+                    Slog.e(TAG, "Failed to clear updates.", e);
+                }
+                mFontCrashDetector.clear();
+            }
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.loadFontFileMap();
+                mSerializedFontMap = buildNewSerializedFontMap();
+            }
+        }
+    }
 
     @NonNull
     public Context getContext() {
         return mContext;
     }
 
-    @NonNull /* package */ SharedMemory getCurrentFontMap() {
+    @Nullable /* package */ SharedMemory getCurrentFontMap() {
         synchronized (FontManagerService.this) {
-            if (mSerializedFontMap == null) {
-                mSerializedFontMap = buildNewSerializedFontMap();
-            }
             return mSerializedFontMap;
         }
     }
@@ -234,9 +259,10 @@
                         FontManager.RESULT_ERROR_VERSION_MISMATCH,
                         "The base config version is older than current.");
             }
-            mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
-            // Create updated font map in the next getSerializedSystemFontMap() call.
-            mSerializedFontMap = null;
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
+                mSerializedFontMap = buildNewSerializedFontMap();
+            }
         }
     }
 
@@ -246,7 +272,12 @@
                     FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
                     "The font updater is disabled.");
         }
-        mUpdatableFontDir.clearUpdates();
+        synchronized (FontManagerService.this) {
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.clearUpdates();
+                mSerializedFontMap = buildNewSerializedFontMap();
+            }
+        }
     }
 
     /* package */ Map<String, File> getFontFileMap() {
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index b0bc65b..0cb7045 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -142,11 +142,9 @@
         mFsverityUtil = fsverityUtil;
         mConfigFile = configFile;
         mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
-        loadFontFileMap();
     }
 
-    private void loadFontFileMap() {
-        // TODO: SIGBUS crash protection
+    /* package */ void loadFontFileMap() {
         synchronized (UpdatableFontDir.this) {
             boolean success = false;
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f6e08fb..2e4200c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -289,6 +289,8 @@
     private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
     private static native boolean nativeIsVibrating(long ptr, int deviceId);
     private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
+    private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
+    private static native int nativeGetBatteryStatus(long ptr, int deviceId);
     private static native void nativeReloadKeyboardLayouts(long ptr);
     private static native void nativeReloadDeviceAliases(long ptr);
     private static native String nativeDump(long ptr);
@@ -2008,6 +2010,18 @@
 
     // Binder call
     @Override
+    public int getBatteryStatus(int deviceId) {
+        return nativeGetBatteryStatus(mPtr, deviceId);
+    }
+
+    // Binder call
+    @Override
+    public int getBatteryCapacity(int deviceId) {
+        return nativeGetBatteryCapacity(mPtr, deviceId);
+    }
+
+    // Binder call
+    @Override
     public void setPointerIconType(int iconId) {
         nativeSetPointerIconType(mPtr, iconId);
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6308ace..e5b5350 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4131,48 +4131,52 @@
     @BinderThread
     @Override
     @GuardedBy("mMethodMap")
-    public void startProtoDump(byte[] protoDump, int source, String where) {
-        if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
-            // Dump not triggered from IMMS, but no proto information provided.
-            return;
-        }
-        ImeTracing tracingInstance = ImeTracing.getInstance();
-        if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
-            return;
-        }
-
-        ProtoOutputStream proto = new ProtoOutputStream();
-        switch (source) {
-            case ImeTracing.IME_TRACING_FROM_CLIENT:
-                final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
-                proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodClientsTraceProto.WHERE, where);
-                proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
-                proto.end(client_token);
-                break;
-            case ImeTracing.IME_TRACING_FROM_IMS:
-                final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
-                proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodServiceTraceProto.WHERE, where);
-                proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
-                proto.end(service_token);
-                break;
-            case IME_TRACING_FROM_IMMS:
-                final long managerservice_token =
-                        proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
-                proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
-                dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
-                proto.end(managerservice_token);
-                break;
-            default:
-                // Dump triggered by a source not recognised.
+    public void startProtoDump(byte[] protoDump, int source, String where,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
+                // Dump not triggered from IMMS, but no proto information provided.
                 return;
-        }
-        tracingInstance.addToBuffer(proto, source);
+            }
+            ImeTracing tracingInstance = ImeTracing.getInstance();
+            if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
+                return;
+            }
+
+            ProtoOutputStream proto = new ProtoOutputStream();
+            switch (source) {
+                case ImeTracing.IME_TRACING_FROM_CLIENT:
+                    final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
+                    proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodClientsTraceProto.WHERE, where);
+                    proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
+                    proto.end(client_token);
+                    break;
+                case ImeTracing.IME_TRACING_FROM_IMS:
+                    final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
+                    proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodServiceTraceProto.WHERE, where);
+                    proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
+                    proto.end(service_token);
+                    break;
+                case IME_TRACING_FROM_IMMS:
+                    final long managerservice_token =
+                            proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
+                    proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
+                    dumpDebug(proto,
+                            InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+                    proto.end(managerservice_token);
+                    break;
+                default:
+                    // Dump triggered by a source not recognised.
+                    return;
+            }
+            tracingInstance.addToBuffer(proto, source);
+        });
     }
 
     @BinderThread
@@ -4183,40 +4187,44 @@
 
     @BinderThread
     @Override
-    public void startImeTrace() {
-        ImeTracing.getInstance().startTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
-        synchronized (mMethodMap) {
-            clients = new ArrayMap<>(mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                try {
-                    state.client.setImeTraceEnabled(true /* enabled */);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+    public void startImeTrace(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            ImeTracing.getInstance().startTrace(null /* printwriter */);
+            ArrayMap<IBinder, ClientState> clients;
+            synchronized (mMethodMap) {
+                clients = new ArrayMap<>(mClients);
+            }
+            for (ClientState state : clients.values()) {
+                if (state != null) {
+                    try {
+                        state.client.setImeTraceEnabled(true /* enabled */);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+                    }
                 }
             }
-        }
+        });
     }
 
     @BinderThread
     @Override
-    public void stopImeTrace() {
-        ImeTracing.getInstance().stopTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
-        synchronized (mMethodMap) {
-            clients = new ArrayMap<>(mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                try {
-                    state.client.setImeTraceEnabled(false /* enabled */);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+    public void stopImeTrace(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            ImeTracing.getInstance().stopTrace(null /* printwriter */);
+            ArrayMap<IBinder, ClientState> clients;
+            synchronized (mMethodMap) {
+                clients = new ArrayMap<>(mClients);
+            }
+            for (ClientState state : clients.values()) {
+                if (state != null) {
+                    try {
+                        state.client.setImeTraceEnabled(false /* enabled */);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+                    }
                 }
             }
-        }
+        });
     }
 
     @GuardedBy("mMethodMap")
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 7f9c766..6fec906 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1868,7 +1868,9 @@
 
         @BinderThread
         @Override
-        public void startProtoDump(byte[] clientProtoDump, int source, String where) {
+        public void startProtoDump(byte[] clientProtoDump, int source, String where,
+                IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
@@ -1879,12 +1881,14 @@
 
         @BinderThread
         @Override
-        public void startImeTrace() {
+        public void startImeTrace(IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
         @Override
-        public void stopImeTrace() {
+        public void stopImeTrace(IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dc1a26a..785e674 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.contexthub;
 
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -320,6 +321,10 @@
             @Override
             public void onNanoAppDisabled(long nanoAppId) {
             }
+
+            @Override
+            public void onClientAuthorizationChanged(long nanoAppId, int authorization) {
+            }
         };
     }
 
@@ -697,6 +702,7 @@
      *
      * @param contextHubId   the ID of the hub this client is attached to
      * @param clientCallback the client interface to register with the service
+     * @param attributionTag an optional attribution tag within the given package
      * @return the generated client interface, null if registration was unsuccessful
      * @throws IllegalArgumentException if contextHubId is not a valid ID
      * @throws IllegalStateException    if max number of clients have already registered
@@ -704,7 +710,8 @@
      */
     @Override
     public IContextHubClient createClient(
-            int contextHubId, IContextHubClientCallback clientCallback) throws RemoteException {
+            int contextHubId, IContextHubClientCallback clientCallback,
+            @Nullable String attributionTag) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -723,13 +730,15 @@
      * @param contextHubId  the ID of the hub this client is attached to
      * @param pendingIntent the PendingIntent associated with this client
      * @param nanoAppId     the ID of the nanoapp PendingIntent events will be sent for
+     * @param attributionTag an optional attribution tag within the given package
      * @return the generated client interface
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
      * @throws IllegalStateException    if there were too many registered clients at the service
      */
     @Override
     public IContextHubClient createPendingIntentClient(
-            int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
+            int contextHubId, PendingIntent pendingIntent, long nanoAppId,
+            @Nullable String attributionTag) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e32c00f..6843733 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -377,7 +377,8 @@
 
     static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
             Adjustment.KEY_CONTEXTUAL_ACTIONS,
-            Adjustment.KEY_TEXT_REPLIES};
+            Adjustment.KEY_TEXT_REPLIES,
+            Adjustment.KEY_NOT_CONVERSATION};
 
     static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
             RoleManager.ROLE_DIALER,
@@ -2313,6 +2314,13 @@
                     } else if ("false".equals(value)) {
                         mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
                     }
+                } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_NOT_CONVERSATION.equals(name)) {
+                    String value = properties.getString(name, null);
+                    if ("true".equals(value)) {
+                        mAssistants.allowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+                    } else if ("false".equals(value)) {
+                        mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+                    }
                 }
             }
         };
@@ -9302,21 +9310,30 @@
                 Slog.v(TAG, "onNotificationEnqueuedLocked() called with: r = [" + r + "]");
             }
             final StatusBarNotification sbn = r.getSbn();
-            notifyAssistantLocked(
-                    sbn,
-                    r.getNotificationType(),
-                    true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
-                        try {
-                            if (debug) {
-                                Slog.v(TAG,
-                                        "calling onNotificationEnqueuedWithChannel " + sbnHolder);
-                            }
-                            assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel());
-                        } catch (RemoteException ex) {
-                            Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                boolean sbnVisible = isVisibleToListener(
+                        sbn, r.getNotificationType(), info)
+                        && info.isSameUser(r.getUserId());
+                if (sbnVisible) {
+                    TrimCache trimCache = new TrimCache(sbn);
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
+                    final StatusBarNotificationHolder sbnHolder =
+                            new StatusBarNotificationHolder(sbnToPost);
+                    try {
+                        if (debug) {
+                            Slog.v(TAG,
+                                    "calling onNotificationEnqueuedWithChannel " + sbnHolder);
                         }
-                    });
+                        final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+                        assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
+                                update);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+                    }
+                }
+            }
         }
 
         @GuardedBy("mNotificationLock")
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index f59934f..d7bc3bb 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -523,27 +523,39 @@
             if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
+                final String cacheKey = getCacheKey(mContext.getUserId(), handle);
                 LookupResult lookupResult = null;
-                final Uri uri = Uri.parse(handle);
-                if ("tel".equals(uri.getScheme())) {
-                    if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
-                    lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
-                } else if ("mailto".equals(uri.getScheme())) {
-                    if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
-                    lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
-                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
-                    if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
-                    lookupResult = searchContacts(mContext, uri);
-                } else {
-                    lookupResult = new LookupResult();  // invalid person for the cache
-                    if (!"name".equals(uri.getScheme())) {
-                        Slog.w(TAG, "unsupported URI " + handle);
+                boolean cacheHit = false;
+                synchronized (mPeopleCache) {
+                    lookupResult = mPeopleCache.get(cacheKey);
+                    if (lookupResult != null && !lookupResult.isExpired()) {
+                        // The name wasn't already added to the cache, no need to retry
+                        cacheHit = true;
+                    }
+                }
+                if (!cacheHit) {
+                    final Uri uri = Uri.parse(handle);
+                    if ("tel".equals(uri.getScheme())) {
+                        if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+                        lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
+                    } else if ("mailto".equals(uri.getScheme())) {
+                        if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+                        lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
+                    } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+                        if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+                        lookupResult = searchContacts(mContext, uri);
+                    } else {
+                        lookupResult = new LookupResult();  // invalid person for the cache
+                        if (!"name".equals(uri.getScheme())) {
+                            Slog.w(TAG, "unsupported URI " + handle);
+                        }
                     }
                 }
                 if (lookupResult != null) {
-                    synchronized (mPeopleCache) {
-                        final String cacheKey = getCacheKey(mContext.getUserId(), handle);
-                        mPeopleCache.put(cacheKey, lookupResult);
+                    if (!cacheHit) {
+                        synchronized (mPeopleCache) {
+                            mPeopleCache.put(cacheKey, lookupResult);
+                        }
                     }
                     if (DEBUG) {
                         Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
@@ -580,4 +592,3 @@
         }
     }
 }
-
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4ef892d..f68113d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18819,9 +18819,11 @@
                     final VersionInfo versionInfo = request.versionInfos.get(installPackageName);
                     final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
                     final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+                    final boolean isRollback = installArgs != null
+                            && installArgs.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
                     final boolean compatMatch = verifySignatures(signatureCheckPs,
                             disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
-                            compareRecover);
+                            compareRecover, isRollback);
                     // The new KeySets will be re-added later in the scanning process.
                     if (compatMatch) {
                         removeAppKeySetData = true;
@@ -19791,6 +19793,7 @@
         final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
         final boolean virtualPreload =
                 ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+        final boolean isRollback = args.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
         @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
         if (args.move != null) {
             // moving a complete application; perform an initial scan on the new install location
@@ -19971,7 +19974,8 @@
                                 parsedPackage);
                         // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(signatureCheckPs, null,
-                                parsedPackage.getSigningDetails(), compareCompat, compareRecover);
+                                parsedPackage.getSigningDetails(), compareCompat, compareRecover,
+                                isRollback);
                         // The new KeySets will be re-added later in the scanning process.
                         if (compatMatch) {
                             synchronized (mLock) {
@@ -20248,15 +20252,23 @@
                                             + pkgName11);
                         }
                     } else {
+                        SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
+                        SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
                         // default to original signature matching
-                        if (!parsedPackage.getSigningDetails().checkCapability(
-                                oldPackage.getSigningDetails(),
+                        if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
                                 SigningDetails.CertCapabilities.INSTALLED_DATA)
-                                && !oldPackage.getSigningDetails().checkCapability(
-                                parsedPackage.getSigningDetails(),
+                                && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
                                 SigningDetails.CertCapabilities.ROLLBACK)) {
-                            throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                    "New package has a different signature: " + pkgName11);
+                            // Allow the update to proceed if this is a rollback and the parsed
+                            // package's current signing key is the current signer or in the lineage
+                            // of the old package; this allows a rollback to a previously installed
+                            // version after an app's signing key has been rotated without requiring
+                            // the rollback capability on the previous signing key.
+                            if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
+                                    parsedPkgSigningDetails)) {
+                                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                        "New package has a different signature: " + pkgName11);
+                            }
                         }
                     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ee94b85..8015063 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -624,7 +624,7 @@
      */
     public static boolean verifySignatures(PackageSetting pkgSetting,
             PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
-            boolean compareCompat, boolean compareRecover)
+            boolean compareCompat, boolean compareRecover, boolean isRollback)
             throws PackageManagerException {
         final String packageName = pkgSetting.name;
         boolean compatMatch = false;
@@ -658,6 +658,13 @@
                 match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
             }
 
+            if (!match && isRollback) {
+                // Since a rollback can only be initiated for an APK previously installed on the
+                // device allow rolling back to a previous signing key even if the rollback
+                // capability has not been granted.
+                match = pkgSetting.signatures.mSigningDetails.hasAncestorOrSelf(parsedSignatures);
+            }
+
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                         "Package " + packageName +
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index e12991a..9560f59 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -563,6 +563,7 @@
                 params.setRequestDowngrade(true);
                 params.setRequiredInstalledVersionCode(
                         pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
+                params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
                 if (isStaged()) {
                     params.setStaged();
                 }
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index 6427ae2..fd12c2d2 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -18,16 +18,28 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Handler;
 import android.os.ParcelUuid;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Tracks a set of Networks underpinning a VcnGatewayConnection.
@@ -38,53 +50,385 @@
  *
  * @hide
  */
-public class UnderlyingNetworkTracker extends Handler {
+public class UnderlyingNetworkTracker {
     @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final UnderlyingNetworkTrackerCallback mCb;
     @NonNull private final Dependencies mDeps;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final SubscriptionManager mSubscriptionManager;
+
+    @NonNull private final SparseArray<NetworkCallback> mCellBringupCallbacks = new SparseArray<>();
+    @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback();
+    @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
+
+    @NonNull private final Set<Integer> mSubIds = new ArraySet<>();
+
+    @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities;
+
+    @Nullable private UnderlyingNetworkRecord mCurrentRecord;
+    @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
 
     public UnderlyingNetworkTracker(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
             @NonNull UnderlyingNetworkTrackerCallback cb) {
-        this(vcnContext, subscriptionGroup, cb, new Dependencies());
+        this(
+                vcnContext,
+                subscriptionGroup,
+                requiredUnderlyingNetworkCapabilities,
+                cb,
+                new Dependencies());
     }
 
     private UnderlyingNetworkTracker(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
             @NonNull UnderlyingNetworkTrackerCallback cb,
             @NonNull Dependencies deps) {
-        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
-        mVcnContext = vcnContext;
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mRequiredUnderlyingNetworkCapabilities =
+                Objects.requireNonNull(
+                        requiredUnderlyingNetworkCapabilities,
+                        "Missing requiredUnderlyingNetworkCapabilities");
         mCb = Objects.requireNonNull(cb, "Missing cb");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+        mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class);
+
+        registerNetworkRequests();
+    }
+
+    private void registerNetworkRequests() {
+        // register bringup requests for underlying Networks
+        mConnectivityManager.requestBackgroundNetwork(
+                getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+        updateSubIdsAndCellularRequests();
+
+        // register Network-selection request used to decide selected underlying Network
+        mConnectivityManager.requestBackgroundNetwork(
+                getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback);
+    }
+
+    private NetworkRequest getWifiNetworkRequest() {
+        return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+    }
+
+    private NetworkRequest getCellNetworkRequestForSubId(int subId) {
+        return getNetworkRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+                .build();
+    }
+
+    private NetworkRequest.Builder getNetworkRequestBase() {
+        NetworkRequest.Builder requestBase = new NetworkRequest.Builder();
+        for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) {
+            requestBase.addCapability(capability);
+        }
+
+        return requestBase
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+    }
+
+    /**
+     * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker.
+     */
+    private void updateSubIdsAndCellularRequests() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        Set<Integer> prevSubIds = new ArraySet<>(mSubIds);
+        mSubIds.clear();
+
+        // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup
+        // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony
+        List<SubscriptionInfo> subInfos =
+                mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup);
+
+        for (SubscriptionInfo subInfo : subInfos) {
+            final int subId = subInfo.getSubscriptionId();
+            mSubIds.add(subId);
+
+            if (!mCellBringupCallbacks.contains(subId)) {
+                final NetworkBringupCallback cb = new NetworkBringupCallback();
+                mCellBringupCallbacks.put(subId, cb);
+
+                mConnectivityManager.requestBackgroundNetwork(
+                        getCellNetworkRequestForSubId(subId), mHandler, cb);
+            }
+        }
+
+        // unregister all NetworkCallbacks for outdated subIds
+        for (final int subId : prevSubIds) {
+            if (!mSubIds.contains(subId)) {
+                final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
+                mConnectivityManager.unregisterNetworkCallback(cb);
+            }
+        }
     }
 
     /** Tears down this Tracker, and releases all underlying network requests. */
-    public void teardown() {}
+    public void teardown() {
+        mVcnContext.ensureRunningOnLooperThread();
 
-    /** An record of a single underlying network, caching relevant fields. */
+        mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback);
+        mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback);
+
+        for (final int subId : mSubIds) {
+            final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
+            mConnectivityManager.unregisterNetworkCallback(cb);
+        }
+        mSubIds.clear();
+    }
+
+    /** Returns whether the currently selected Network matches the given network. */
+    private static boolean isSameNetwork(
+            @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) {
+        return recordInProgress != null && recordInProgress.getNetwork().equals(network);
+    }
+
+    /** Notify the Callback if a full UnderlyingNetworkRecord exists. */
+    private void maybeNotifyCallback() {
+        // Only forward this update if a complete record has been received
+        if (!mRecordInProgress.isValid()) {
+            return;
+        }
+
+        // Only forward this update if the updated record differs form the current record
+        UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build();
+        if (!updatedRecord.equals(mCurrentRecord)) {
+            mCurrentRecord = updatedRecord;
+
+            mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+        }
+    }
+
+    private void handleNetworkAvailable(@NonNull Network network) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mRecordInProgress = new UnderlyingNetworkRecord.Builder(network);
+    }
+
+    private void handleNetworkLost(@NonNull Network network) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Non-underlying Network lost");
+            return;
+        }
+
+        mRecordInProgress = null;
+        mCurrentRecord = null;
+        mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */);
+    }
+
+    private void handleCapabilitiesChanged(
+            @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to NetworkCapabilities");
+            return;
+        }
+
+        mRecordInProgress.setNetworkCapabilities(networkCapabilities);
+
+        maybeNotifyCallback();
+    }
+
+    private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to isSuspended");
+            return;
+        }
+
+        final NetworkCapabilities newCaps =
+                new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
+        if (isSuspended) {
+            newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+        } else {
+            newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+        }
+
+        handleCapabilitiesChanged(network, newCaps);
+    }
+
+    private void handlePropertiesChanged(
+            @NonNull Network network, @NonNull LinkProperties linkProperties) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to LinkProperties");
+            return;
+        }
+
+        mRecordInProgress.setLinkProperties(linkProperties);
+
+        maybeNotifyCallback();
+    }
+
+    private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to isBlocked");
+            return;
+        }
+
+        mRecordInProgress.setIsBlocked(isBlocked);
+
+        maybeNotifyCallback();
+    }
+
+    /**
+     * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
+     *
+     * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
+     * reaped, and no action is taken on any events firing.
+     */
+    @VisibleForTesting
+    class NetworkBringupCallback extends NetworkCallback {}
+
+    /**
+     * RouteSelectionCallback is used to select the "best" underlying Network.
+     *
+     * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
+     * truth.
+     */
+    @VisibleForTesting
+    class RouteSelectionCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            handleNetworkAvailable(network);
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            handleNetworkLost(network);
+        }
+
+        @Override
+        public void onCapabilitiesChanged(
+                @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+            handleCapabilitiesChanged(network, networkCapabilities);
+        }
+
+        @Override
+        public void onNetworkSuspended(@NonNull Network network) {
+            handleNetworkSuspended(network, true /* isSuspended */);
+        }
+
+        @Override
+        public void onNetworkResumed(@NonNull Network network) {
+            handleNetworkSuspended(network, false /* isSuspended */);
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(
+                @NonNull Network network, @NonNull LinkProperties linkProperties) {
+            handlePropertiesChanged(network, linkProperties);
+        }
+
+        @Override
+        public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
+            handleNetworkBlocked(network, isBlocked);
+        }
+    }
+
+    /** A record of a single underlying network, caching relevant fields. */
     public static class UnderlyingNetworkRecord {
         @NonNull public final Network network;
         @NonNull public final NetworkCapabilities networkCapabilities;
         @NonNull public final LinkProperties linkProperties;
-        public final boolean blocked;
+        public final boolean isBlocked;
 
         @VisibleForTesting(visibility = Visibility.PRIVATE)
         UnderlyingNetworkRecord(
                 @NonNull Network network,
                 @NonNull NetworkCapabilities networkCapabilities,
                 @NonNull LinkProperties linkProperties,
-                boolean blocked) {
+                boolean isBlocked) {
             this.network = network;
             this.networkCapabilities = networkCapabilities;
             this.linkProperties = linkProperties;
-            this.blocked = blocked;
+            this.isBlocked = isBlocked;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof UnderlyingNetworkRecord)) return false;
+            final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
+
+            return network.equals(that.network)
+                    && networkCapabilities.equals(that.networkCapabilities)
+                    && linkProperties.equals(that.linkProperties)
+                    && isBlocked == that.isBlocked;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
+        }
+
+        /** Builder to incrementally construct an UnderlyingNetworkRecord. */
+        private static class Builder {
+            @NonNull private final Network mNetwork;
+
+            @Nullable private NetworkCapabilities mNetworkCapabilities;
+            @Nullable private LinkProperties mLinkProperties;
+            boolean mIsBlocked;
+            boolean mWasIsBlockedSet;
+
+            private Builder(@NonNull Network network) {
+                mNetwork = network;
+            }
+
+            @NonNull
+            private Network getNetwork() {
+                return mNetwork;
+            }
+
+            private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+                mNetworkCapabilities = networkCapabilities;
+            }
+
+            @Nullable
+            private NetworkCapabilities getNetworkCapabilities() {
+                return mNetworkCapabilities;
+            }
+
+            private void setLinkProperties(@NonNull LinkProperties linkProperties) {
+                mLinkProperties = linkProperties;
+            }
+
+            private void setIsBlocked(boolean isBlocked) {
+                mIsBlocked = isBlocked;
+                mWasIsBlockedSet = true;
+            }
+
+            private boolean isValid() {
+                return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
+            }
+
+            private UnderlyingNetworkRecord build() {
+                return new UnderlyingNetworkRecord(
+                        mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
+            }
         }
     }
 
@@ -95,9 +439,10 @@
          *
          * <p>This callback does NOT signal a mobility event.
          *
-         * @param underlying The details of the new underlying network
+         * @param underlyingNetworkRecord The details of the new underlying network
          */
-        void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+        void onSelectedUnderlyingNetworkChanged(
+                @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
     }
 
     private static class Dependencies {}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 9d21b92..132883e 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -168,8 +168,8 @@
             @NonNull NetworkRequest request, int score, int providerId) {
         if (score > getNetworkScore()) {
             Slog.v(getLogTag(),
-                    "Request " + request.requestId + " already satisfied by higher-scoring ("
-                            + score + ") network from provider " + providerId);
+                    "Request already satisfied by higher-scoring (" + score + ") network from "
+                            + "provider " + providerId + ": " + request);
             return;
         }
 
@@ -177,8 +177,7 @@
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
             if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                 Slog.v(getLogTag(),
-                        "Request " + request.requestId
-                                + " satisfied by existing VcnGatewayConnection");
+                        "Request already satisfied by existing VcnGatewayConnection: " + request);
                 return;
             }
         }
@@ -202,12 +201,12 @@
 
     private boolean requestSatisfiedByGatewayConnectionConfig(
             @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
-        final NetworkCapabilities configCaps = new NetworkCapabilities();
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
         for (int cap : config.getAllExposedCapabilities()) {
-            configCaps.addCapability(cap);
+            builder.addCapability(cap);
         }
 
-        return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
+        return request.canBeSatisfiedBy(builder.build());
     }
 
     private String getLogTag() {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index dba59bd..7399e56 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -55,4 +55,15 @@
     public VcnNetworkProvider getVcnNetworkProvider() {
         return mVcnNetworkProvider;
     }
+
+    /**
+     * Verifies that the caller is running on the VcnContext Thread.
+     *
+     * @throwsIllegalStateException if the caller is not running on the VcnContext Thread.
+     */
+    public void ensureRunningOnLooperThread() {
+        if (getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException("Not running on VcnMgmtSvc thread");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 703bfab..39c9606 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -65,6 +65,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -476,7 +477,10 @@
 
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
-                        mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback);
+                        mVcnContext,
+                        subscriptionGroup,
+                        mConnectionConfig.getAllUnderlyingCapabilities(),
+                        mUnderlyingNetworkTrackerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
 
         IpSecTunnelInterface iface;
@@ -963,18 +967,18 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static NetworkCapabilities buildNetworkCapabilities(
             @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
-        final NetworkCapabilities caps = new NetworkCapabilities();
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
 
-        caps.addTransportType(TRANSPORT_CELLULAR);
-        caps.addCapability(NET_CAPABILITY_NOT_CONGESTED);
-        caps.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        builder.addTransportType(TRANSPORT_CELLULAR);
+        builder.addCapability(NET_CAPABILITY_NOT_CONGESTED);
+        builder.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
 
         // Add exposed capabilities
         for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
-            caps.addCapability(cap);
+            builder.addCapability(cap);
         }
 
-        return caps;
+        return builder.build();
     }
 
     private static LinkProperties buildConnectedLinkProperties(
@@ -1134,8 +1138,10 @@
         public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
                 VcnContext vcnContext,
                 ParcelUuid subscriptionGroup,
+                Set<Integer> requiredUnderlyingNetworkCapabilities,
                 UnderlyingNetworkTrackerCallback callback) {
-            return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+            return new UnderlyingNetworkTracker(
+                    vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback);
         }
 
         /** Builds a new IkeSession. */
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index 7f5b23c..b9babae 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -21,9 +21,9 @@
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
 import android.os.Looper;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import java.util.Objects;
 import java.util.Set;
@@ -40,7 +40,13 @@
     private static final String TAG = VcnNetworkProvider.class.getSimpleName();
 
     private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
-    private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();
+
+    /**
+     * Cache of NetworkRequest(s), scores and network providers, keyed by NetworkRequest
+     *
+     * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
+     */
+    private final ArrayMap<NetworkRequest, NetworkRequestEntry> mRequests = new ArrayMap<>();
 
     public VcnNetworkProvider(Context context, Looper looper) {
         super(context, looper, VcnNetworkProvider.class.getSimpleName());
@@ -51,8 +57,8 @@
         mListeners.add(listener);
 
         // Send listener all cached requests
-        for (int i = 0; i < mRequests.size(); i++) {
-            notifyListenerForEvent(listener, mRequests.valueAt(i));
+        for (NetworkRequestEntry entry : mRequests.values()) {
+            notifyListenerForEvent(listener, entry);
         }
     }
 
@@ -75,7 +81,9 @@
                         request, score, providerId));
 
         final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
-        mRequests.put(request.requestId, entry);
+
+        // NetworkRequests are immutable once created, and therefore can be used as stable keys.
+        mRequests.put(request, entry);
 
         // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
         // Default Data Sub, or similar)
@@ -86,7 +94,7 @@
 
     @Override
     public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
-        mRequests.remove(request.requestId);
+        mRequests.remove(request);
     }
 
     private static class NetworkRequestEntry {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index fe3b03ab..e0f5408 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -138,6 +138,11 @@
         return mStatus != Status.RUNNING;
     }
 
+    /** Return true is effect is a repeating vibration. */
+    public boolean isRepeating() {
+        return mEffect.getDuration() == Long.MAX_VALUE;
+    }
+
     /** Return the effect that should be played by this vibration. */
     @Nullable
     public CombinedVibrationEffect getEffect() {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 536375f..8910bdf 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -34,10 +34,12 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.VibratorServiceDumpProto;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -340,6 +342,24 @@
                 + '}';
     }
 
+    /** Write current settings into given {@link ProtoOutputStream}. */
+    public void dumpProto(ProtoOutputStream proto) {
+        synchronized (mLock) {
+            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+                    mHapticFeedbackIntensity);
+            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultHapticFeedbackIntensity());
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+                    mNotificationIntensity);
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultNotificationVibrationIntensity());
+            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+                    mRingIntensity);
+            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultRingVibrationIntensity());
+        }
+    }
+
     private void notifyListeners() {
         List<OnVibratorSettingsChanged> currentListeners;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 5355252..4f2fc86 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -114,12 +114,11 @@
         }
     }
 
-    public Vibration getVibration() {
-        return mVibration;
-    }
-
     @Override
     public void binderDied() {
+        if (DEBUG) {
+            Slog.d(TAG, "Binder died, cancelling vibration...");
+        }
         cancel();
     }
 
@@ -150,12 +149,19 @@
     /** Notify current vibration that a step has completed on given vibrator. */
     public void vibratorComplete(int vibratorId) {
         synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
+            }
             if (mCurrentVibrateStep != null) {
                 mCurrentVibrateStep.vibratorComplete(vibratorId);
             }
         }
     }
 
+    public Vibration getVibration() {
+        return mVibration;
+    }
+
     @VisibleForTesting
     SparseArray<VibratorController> getVibrators() {
         return mVibrators;
@@ -467,7 +473,7 @@
                     noteVibratorOff();
                 }
                 if (DEBUG) {
-                    Slog.d(TAG, "SingleVibrateStep step done.");
+                    Slog.d(TAG, "SingleVibrateStep done.");
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
deleted file mode 100644
index 3c8cf4e..0000000
--- a/services/core/java/com/android/server/wm/BarController.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-
-/**
- * Controls state/behavior specific to a system bar window.
- */
-public class BarController {
-    private final int mWindowType;
-
-    private final Rect mContentFrame = new Rect();
-
-    BarController(int windowType) {
-        mWindowType = windowType;
-    }
-
-    /**
-     * Sets the frame within which the bar will display its content.
-     *
-     * This is used to determine if letterboxes interfere with the display of such content.
-     */
-    void setContentFrame(Rect frame) {
-        mContentFrame.set(frame);
-    }
-
-    private Rect getContentFrame(@NonNull WindowState win) {
-        final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
-        return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
-    }
-
-    boolean isLightAppearanceAllowed(WindowState win) {
-        if (win == null) {
-            return true;
-        }
-        return !win.isLetterboxedOverlappingWith(getContentFrame(win));
-    }
-
-    /**
-     * @return {@code true} if bar is allowed to be fully transparent when given window is show.
-     *
-     * <p>Prevents showing a transparent bar over a letterboxed activity which can make
-     * notification icons or navigation buttons unreadable due to contrast between letterbox
-     * background and an activity. For instance, this happens when letterbox background is solid
-     * black while activity is white. To resolve this, only semi-transparent bars are allowed to
-     * be drawn over letterboxed activity.
-     */
-    boolean isFullyTransparentAllowed(WindowState win) {
-        if (win == null) {
-            return true;
-        }
-        return win.isFullyTransparentBarAllowed(getContentFrame(win));
-    }
-}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index dbad8b3..a725dd3 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -24,6 +24,9 @@
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.res.CompatibilityInfo;
@@ -32,6 +35,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -63,6 +67,58 @@
     // Compatibility state: compatibility mode is enabled.
     private static final int COMPAT_FLAG_ENABLED = 1<<1;
 
+    /**
+     * CompatModePackages#DOWNSCALED is the gatekeeper of all per-app buffer downscaling
+     * changes.  Disabling this change will prevent the following scaling factors from working:
+     * CompatModePackages#DOWNSCALE_87_5
+     * CompatModePackages#DOWNSCALE_75
+     * CompatModePackages#DOWNSCALE_62_5
+     * CompatModePackages#DOWNSCALE_50
+     *
+     * If CompatModePackages#DOWNSCALED is enabled for an app package, then the app will be forcibly
+     * resized to the highest enabled scaling factor e.g. 87.5% if both 87.5% and 75% were
+     * enabled.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALED = 168419799L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_87_5 for a package will force the app to assume it's
+     * running on a display with 87.5% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_87_5 = 176926753L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_75 for a package will force the app to assume it's
+     * running on a display with 75% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_75 = 176926829L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_62_5 for a package will force the app to assume it's
+     * running on a display with 62.5% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_62_5 = 176926771L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_50 for a package will force the app to assume it's
+     * running on a display with 50% vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_50 = 176926741L;
+
     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
 
     private static final int MSG_WRITE = 300;
@@ -191,11 +247,39 @@
         mHandler.sendMessageDelayed(msg, 10000);
     }
 
+    float getCompatScale(String packageName, int uid) {
+        if (!CompatChanges.isChangeEnabled(
+                DOWNSCALED, packageName, UserHandle.getUserHandleForUid(uid))) {
+            return 1f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_87_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // 8/7 == (1 / 0.875) ~= 1.14285714286
+            return 8f / 7f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_75, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // 4/3 == (1 / 0.75) ~= 1.333333333
+            return 4f / 3f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_62_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // (1 / 0.625) == 1.6
+            return 1.6f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_50, packageName, UserHandle.getUserHandleForUid(uid))) {
+            return 2f;
+        }
+        return 1f;
+    }
+
     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
         final Configuration globalConfig = mService.getGlobalConfiguration();
+        final float requestedScale = getCompatScale(ai.packageName, ai.uid);
         CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
                 globalConfig.smallestScreenWidthDp,
-                (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
+                (getPackageFlags(ai.packageName) & COMPAT_FLAG_ENABLED) != 0, requestedScale);
         //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
         return ci;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 15483cb..f075d85 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -462,6 +462,23 @@
     }
 
     @Override
+    void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+        super.resolveOverrideConfiguration(newParentConfiguration);
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        final Rect overrideBounds = resolvedConfig.windowConfiguration.getBounds();
+        final Rect overrideAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+
+        // If there is no override of appBounds, restrict appBounds to the override bounds.
+        if (!overrideBounds.isEmpty() && (overrideAppBounds == null || overrideAppBounds.isEmpty())
+                && parentAppBounds != null && !parentAppBounds.isEmpty()) {
+            final Rect appBounds = new Rect(overrideBounds);
+            appBounds.intersect(parentAppBounds);
+            resolvedConfig.windowConfiguration.setAppBounds(appBounds);
+        }
+    }
+
+    @Override
     boolean isOrganized() {
         return mOrganizer != null;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 11cc2c6..d5d06f9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -670,8 +670,9 @@
     // Used in updating override configurations
     private final Configuration mTempConfig = new Configuration();
 
-    // Used in performing layout
-    private boolean mTmpWindowsBehindIme;
+    // Used in performing layout, to record the insets provided by other windows above the current
+    // window.
+    private InsetsState mTmpAboveInsetsState = new InsetsState();
 
     /**
      * Used to prevent recursions when calling
@@ -770,17 +771,11 @@
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
-        // Sets mBehindIme for each window. Windows behind IME can get IME insets.
-        if (w.mBehindIme != mTmpWindowsBehindIme) {
-            w.mBehindIme = mTmpWindowsBehindIme;
-            if (getInsetsStateController().getRawInsetsState().getSourceOrDefaultVisibility(
-                    ITYPE_IME)) {
-                // If IME is invisible, behind IME or not doesn't make the insets different.
-                mWinInsetsChanged.add(w);
-            }
-        }
-        if (w == mInputMethodWindow) {
-            mTmpWindowsBehindIme = true;
+        // Sets mAboveInsets for each window. Windows behind the window providing the insets can
+        // receive the insets.
+        if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) {
+            w.mAboveInsetsState.set(mTmpAboveInsetsState);
+            mWinInsetsChanged.add(w);
         }
 
         // If this view is GONE, then skip it -- keep the current frame, and let the caller know
@@ -816,8 +811,16 @@
                     + " mContainingFrame=" + w.getContainingFrame()
                     + " mDisplayFrame=" + w.getDisplayFrame());
         }
+        provideInsetsByWindow(w);
     };
 
+    private void provideInsetsByWindow(WindowState w) {
+        for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) {
+            final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i);
+            mTmpAboveInsetsState.addSource(providedSource);
+        }
+    }
+
     private final Consumer<WindowState> mPerformLayoutAttached = w -> {
         if (w.mLayoutAttached) {
             if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -4283,14 +4286,20 @@
                     + " dh=" + mDisplayInfo.logicalHeight);
         }
 
+        // Used to indicate that we have processed the insets windows. This needs to be after
+        // beginLayoutLw to ensure the raw insets state display related info is initialized.
+        final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState();
+        mTmpAboveInsetsState = new InsetsState();
+        mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame());
+        mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout());
+        mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState);
+
         int seq = mLayoutSeq + 1;
         if (seq < 0) seq = 0;
         mLayoutSeq = seq;
 
         mTmpInitial = initial;
 
-        // Used to indicate that we have processed the IME window.
-        mTmpWindowsBehindIme = false;
 
         // First perform layout of any root windows (not attached to another window).
         forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f52cb09..ed97848 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -86,6 +86,7 @@
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
@@ -305,8 +306,7 @@
 
     private boolean mLastImmersiveMode;
 
-    private final BarController mStatusBarController;
-    private final BarController mNavigationBarController;
+    private final SparseArray<Rect> mBarContentFrames = new SparseArray<>();
 
     // The windows we were told about in focusChanged.
     private WindowState mFocusedWindow;
@@ -432,8 +432,8 @@
 
         final int displayId = displayContent.getDisplayId();
 
-        mStatusBarController = new BarController(TYPE_STATUS_BAR);
-        mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
+        mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+        mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
 
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -1253,11 +1253,6 @@
                 displayFrames.mDisplayCutoutSafe.top);
     }
 
-    @VisibleForTesting
-    BarController getStatusBarController() {
-        return mStatusBarController;
-    }
-
     WindowState getStatusBar() {
         return mStatusBar != null ? mStatusBar : mStatusBarAlt;
     }
@@ -1473,7 +1468,7 @@
         mSystemGestures.screenHeight = info.logicalHeight;
     }
 
-    private void layoutStatusBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+    private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) {
         // decide where the status bar goes ahead of time
         if (mStatusBar == null) {
             return;
@@ -1500,21 +1495,16 @@
                     statusBarBottom);
         }
 
-        // Tell the bar controller where the collapsed status bar content is.
         sTmpRect.set(windowFrames.mFrame);
         sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
         sTmpRect.top = windowFrames.mFrame.top; // Ignore top display cutout inset
         sTmpRect.bottom = statusBarBottom; // Use collapsed status bar size
-        if (simulatedContentFrame != null) {
-            simulatedContentFrame.set(sTmpRect);
-        } else {
-            mStatusBarController.setContentFrame(sTmpRect);
-        }
+        contentFrame.set(sTmpRect);
     }
 
-    private void layoutNavigationBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+    private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
         if (mNavigationBar == null) {
-            return;
+            return NAV_BAR_INVALID;
         }
 
         final int uiMode = mDisplayContent.getConfiguration().uiMode;
@@ -1552,17 +1542,12 @@
         windowFrames.setFrames(navigationFrame /* parentFrame */,
                 navigationFrame /* displayFrame */);
         mNavigationBar.computeFrameAndUpdateSourceFrame();
-        final Rect contentFrame =  sTmpRect;
-        contentFrame.set(windowFrames.mFrame);
-        contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
-        if (simulatedContentFrame != null) {
-            simulatedContentFrame.set(contentFrame);
-        } else {
-            mNavigationBarPosition = navBarPosition;
-            mNavigationBarController.setContentFrame(contentFrame);
-        }
+        sTmpRect.set(windowFrames.mFrame);
+        sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+        contentFrame.set(sTmpRect);
 
         if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
+        return navBarPosition;
     }
 
     private boolean canReceiveInput(WindowState win) {
@@ -1609,11 +1594,12 @@
      */
     public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
         if (win == mNavigationBar) {
-            layoutNavigationBar(displayFrames, null /* simulatedContentFrame */);
+            mNavigationBarPosition = layoutNavigationBar(displayFrames,
+                    mBarContentFrames.get(TYPE_NAVIGATION_BAR));
             return;
         }
         if ((win == mStatusBar && !canReceiveInput(win))) {
-            layoutStatusBar(displayFrames, null /* simulatedContentFrame */);
+            layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
             return;
         }
         final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -2622,7 +2608,7 @@
                 // Otherwise if it's dimming, clear the light flag.
                 appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
             }
-            if (!mStatusBarController.isLightAppearanceAllowed(statusColorWin)) {
+            if (!isLightBarAllowed(statusColorWin, TYPE_STATUS_BAR)) {
                 appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
             }
         }
@@ -2685,7 +2671,7 @@
                 // Clear the light flag for dimming window.
                 appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
             }
-            if (!mNavigationBarController.isLightAppearanceAllowed(navColorWin)) {
+            if (!isLightBarAllowed(navColorWin, TYPE_NAVIGATION_BAR)) {
                 appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
             }
         }
@@ -2739,7 +2725,36 @@
         return appearance;
     }
 
-    private boolean drawsBarBackground(WindowState win, BarController controller) {
+    private boolean isLightBarAllowed(WindowState win, int windowType) {
+        if (win == null) {
+            return true;
+        }
+        return !win.isLetterboxedOverlappingWith(getBarContentFrameForWindow(win, windowType));
+    }
+
+    private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
+        final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
+        return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+    }
+
+    /**
+     * @return {@code true} if bar is allowed to be fully transparent when given window is show.
+     *
+     * <p>Prevents showing a transparent bar over a letterboxed activity which can make
+     * notification icons or navigation buttons unreadable due to contrast between letterbox
+     * background and an activity. For instance, this happens when letterbox background is solid
+     * black while activity is white. To resolve this, only semi-transparent bars are allowed to
+     * be drawn over letterboxed activity.
+     */
+    @VisibleForTesting
+    boolean isFullyTransparentAllowed(WindowState win, int windowType) {
+        if (win == null) {
+            return true;
+        }
+        return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, windowType));
+    }
+
+    private boolean drawsBarBackground(WindowState win) {
         if (win == null) {
             return true;
         }
@@ -2752,27 +2767,19 @@
         return forceDrawsSystemBars || drawsSystemBars;
     }
 
-    private boolean drawsStatusBarBackground(WindowState win) {
-        return drawsBarBackground(win, mStatusBarController);
-    }
-
-    private boolean drawsNavigationBarBackground(WindowState win) {
-        return drawsBarBackground(win, mNavigationBarController);
-    }
-
     /** @return the current visibility flags with the status bar opacity related flags toggled. */
     private int configureStatusBarOpacity(int appearance) {
         final boolean fullscreenDrawsBackground =
-                drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
+                drawsBarBackground(mTopFullscreenOpaqueWindowState);
         final boolean dockedDrawsBackground =
-                drawsStatusBarBackground(mTopDockedOpaqueWindowState);
+                drawsBarBackground(mTopDockedOpaqueWindowState);
 
         if (fullscreenDrawsBackground && dockedDrawsBackground) {
             appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
         }
 
-        if (!mStatusBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
-                || !mStatusBarController.isFullyTransparentAllowed(mTopDockedOpaqueWindowState)) {
+        if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR)
+                || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
         }
 
@@ -2788,9 +2795,9 @@
         final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
                 .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
         final boolean fullscreenDrawsBackground =
-                drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
+                drawsBarBackground(mTopFullscreenOpaqueWindowState);
         final boolean dockedDrawsBackground =
-                drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
+                drawsBarBackground(mTopDockedOpaqueWindowState);
 
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
             if (fullscreenDrawsBackground && dockedDrawsBackground) {
@@ -2818,9 +2825,8 @@
             }
         }
 
-        if (!mNavigationBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
-                || !mNavigationBarController.isFullyTransparentAllowed(
-                        mTopDockedOpaqueWindowState)) {
+        if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
+                || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
         }
 
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1692df6..c6c7fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -151,6 +151,7 @@
             // animate-out as new one animates-in.
             mWin.cancelAnimation();
             mWin.mPendingPositionChanged = null;
+            mWin.mProvidedInsetsSources.remove(mSource.getType());
         }
         ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
         mWin = win;
@@ -160,11 +161,14 @@
             setServerVisible(false);
             mSource.setFrame(new Rect());
             mSource.setVisibleFrame(null);
-        } else if (mControllable) {
-            mWin.setControllableInsetProvider(this);
-            if (mPendingControlTarget != null) {
-                updateControlForTarget(mPendingControlTarget, true /* force */);
-                mPendingControlTarget = null;
+        } else {
+            mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
+            if (mControllable) {
+                mWin.setControllableInsetProvider(this);
+                if (mPendingControlTarget != null) {
+                    updateControlForTarget(mPendingControlTarget, true /* force */);
+                    mPendingControlTarget = null;
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 267f677..3ba7b7d 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -104,6 +104,8 @@
      * visible to the target. e.g., the source which represents the target window itself, and the
      * IME source when the target is above IME. We also need to exclude certain types of insets
      * source for client within specific windowing modes.
+     * This is to get the insets for a window layout on the screen. If the window is not there, use
+     * the {@link #getInsetsForWindowMetrics} to get insets instead.
      *
      * @param target The window associate with the perspective.
      * @return The state stripped of the necessary information.
@@ -117,8 +119,8 @@
         final @InternalInsetsType int type = provider != null
                 ? provider.getSource().getType() : ITYPE_INVALID;
         return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
-                isAboveIme(target),
-                target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState);
+                target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() :
+                target.mAboveInsetsState);
     }
 
     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
@@ -133,19 +135,7 @@
         final @WindowingMode int windowingMode = token != null
                 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
-        return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState);
-    }
-
-    private boolean isAboveIme(WindowContainer target) {
-        final WindowState imeWindow = mDisplayContent.mInputMethodWindow;
-        if (target == null || imeWindow == null) {
-            return false;
-        }
-        if (target instanceof WindowState) {
-            final WindowState win = (WindowState) target;
-            return win.needsRelativeLayeringToIme() || !win.mBehindIme;
-        }
-        return false;
+        return getInsetsForTarget(type, windowingMode, alwaysOnTop, mState);
     }
 
     private static @InternalInsetsType
@@ -181,10 +171,12 @@
      * @see #getInsetsForWindowMetrics
      */
     private InsetsState getInsetsForTarget(@InternalInsetsType int type,
-            @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme,
-            @NonNull InsetsState state) {
+            @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) {
+        boolean stateCopied = false;
+
         if (type != ITYPE_INVALID) {
             state = new InsetsState(state);
+            stateCopied = true;
             state.removeSource(type);
 
             // Navigation bar doesn't get influenced by anything else
@@ -219,23 +211,15 @@
 
         if (WindowConfiguration.isFloating(windowingMode)
                 || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
-            state = new InsetsState(state);
+            if (!stateCopied) {
+                state = new InsetsState(state);
+                stateCopied = true;
+            }
             state.removeSource(ITYPE_STATUS_BAR);
             state.removeSource(ITYPE_NAVIGATION_BAR);
             state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
         }
 
-        if (aboveIme) {
-            InsetsSource imeSource = state.peekSource(ITYPE_IME);
-            if (imeSource != null && imeSource.isVisible()) {
-                imeSource = new InsetsSource(imeSource);
-                imeSource.setVisible(false);
-                imeSource.setFrame(0, 0, 0, 0);
-                state = new InsetsState(state);
-                state.addSource(imeSource);
-            }
-        }
-
         return state;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1597604..0250376 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -212,6 +212,7 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
@@ -440,6 +441,7 @@
     float mGlobalScale=1;
     float mLastGlobalScale=1;
     float mInvGlobalScale=1;
+    float mOverrideScale = 1;
     float mHScale=1, mVScale=1;
     float mLastHScale=1, mLastVScale=1;
     final Matrix mTmpMatrix = new Matrix();
@@ -647,9 +649,14 @@
     boolean mSeamlesslyRotated = false;
 
     /**
-     * Indicates if this window is behind IME. Only windows behind IME can get insets from IME.
+     * The insets state of sources provided by windows above the current window.
      */
-    boolean mBehindIme = false;
+    InsetsState mAboveInsetsState = new InsetsState();
+
+    /**
+     * The insets sources provided by this window.
+     */
+    ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>();
 
     /**
      * Surface insets from the previous call to relayout(), used to track
@@ -1008,6 +1015,8 @@
         mLastRequestedWidth = 0;
         mLastRequestedHeight = 0;
         mLayer = 0;
+        mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
+                mAttrs.packageName, s.mUid);
 
         // Make sure we initial all fields before adding to parentWindow, to prevent exception
         // during onDisplayChanged.
@@ -1040,8 +1049,15 @@
         mSession.windowAddedLocked(mAttrs.packageName);
     }
 
+    /**
+     * @return {@code true} if the application runs in size compatibility mode or has an app level
+     * scaling override set.
+     * @see CompatModePackages#getCompatScale
+     * @see android.content.res.CompatibilityInfo#supportsScreen
+     * @see ActivityRecord#inSizeCompatMode()
+     */
     boolean inSizeCompatMode() {
-        return inSizeCompatMode(mAttrs, mActivityRecord);
+        return mOverrideScale != 1f || inSizeCompatMode(mAttrs, mActivityRecord);
     }
 
     /**
@@ -1676,7 +1692,13 @@
 
     void prelayout() {
         if (inSizeCompatMode()) {
-            mGlobalScale = mToken.getSizeCompatScale();
+            if (mOverrideScale != 1f) {
+                mGlobalScale = mToken.hasSizeCompatBounds()
+                        ? mToken.getSizeCompatScale() * mOverrideScale
+                        : mOverrideScale;
+            } else {
+                mGlobalScale = mToken.getSizeCompatScale();
+            }
             mInvGlobalScale = 1 / mGlobalScale;
         } else {
             mGlobalScale = mInvGlobalScale = 1;
@@ -2634,8 +2656,7 @@
         // scaling but the existing logic doesn't expect that. The result is that the already-
         // scaled region ends up getting sent to surfaceflinger which then applies the scale
         // (again). Until this is resolved, apply an inverse-scale here.
-        if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
-                && mGlobalScale != 1.f) {
+        if (mInvGlobalScale != 1.f) {
             region.scale(mInvGlobalScale);
         }
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dc15b07..5b587e9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,14 +26,14 @@
 // Log debug messages about InputDispatcherPolicy
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
-
-#include <atomic>
-#include <cinttypes>
-#include <limits.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
+#include <limits.h>
+#include <atomic>
+#include <cinttypes>
 
 #include <utils/Log.h>
 #include <utils/Looper.h>
@@ -46,6 +46,7 @@
 #include <input/SpriteController.h>
 #include <ui/Region.h>
 
+#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
 #include <inputflinger/InputManager.h>
 
 #include <android_os_MessageQueue.h>
@@ -1908,6 +1909,20 @@
     return vibIdArray;
 }
 
+static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryCapacity(deviceId);
+    return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
+}
+
+static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryStatus(deviceId);
+    return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
+}
+
 static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
         jclass /* clazz */, jlong ptr) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2163,6 +2178,8 @@
         {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
         {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
         {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
+        {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
+        {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
         {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
         {"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
         {"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c3bb757..14376d3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -35,10 +35,8 @@
 import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS;
 import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT;
 import static android.app.admin.DevicePolicyManager.CODE_OK;
 import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
-import static android.app.admin.DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
 import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
@@ -1380,11 +1378,6 @@
             SystemProperties.set(key, value);
         }
 
-        // TODO (b/137101239): clean up split system user codes
-        boolean userManagerIsSplitSystemUser() {
-            return UserManager.isSplitSystemUser();
-        }
-
         boolean userManagerIsHeadlessSystemUserMode() {
             return UserManager.isHeadlessSystemUserMode();
         }
@@ -7404,48 +7397,18 @@
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
 
+    // TODO (b/137101239): remove this method in follow-up CL
+    // since it's only used for split system user.
     @Override
     public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
-        if (!mHasFeature) {
-            return;
-        }
-        Objects.requireNonNull(who, "ComponentName is null");
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
-        // Allow setting this policy to true only if there is a split system user.
-        if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
-            throw new UnsupportedOperationException(
-                    "Cannot force ephemeral users on systems without split system user.");
-        }
-        boolean removeAllUsers = false;
-        synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
-                deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
-                saveSettingsLocked(caller.getUserId());
-                mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
-                removeAllUsers = forceEphemeralUsers;
-            }
-        }
-        if (removeAllUsers) {
-            mInjector.binderWithCleanCallingIdentity(() -> mUserManagerInternal.removeAllUsers());
-        }
+        throw new UnsupportedOperationException("This method was used by split system user only.");
     }
 
+    // TODO (b/137101239): remove this method in follow-up CL
+    // since it's only used for split system user.
     @Override
     public boolean getForceEphemeralUsers(ComponentName who) {
-        if (!mHasFeature) {
-            return false;
-        }
-        Objects.requireNonNull(who, "ComponentName is null");
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
-        synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            return deviceOwner.forceEphemeralUsers;
-        }
+        throw new UnsupportedOperationException("This method was used by split system user only.");
     }
 
     @Override
@@ -12733,13 +12696,6 @@
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(callingUserId);
-                // TODO (b/137101239): clean up split system user codes
-                //  ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
-                //  only supported on split-user systems.
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
-                    return checkManagedUserProvisioningPreCondition(callingUserId);
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
-                    return checkManagedShareableDeviceProvisioningPreCondition(callingUserId);
             }
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -12775,14 +12731,12 @@
             }
         }
 
-        // TODO (b/137101239): clean up split system user codes
         if (isAdb) {
             // If shell command runs after user setup completed check device status. Otherwise, OK.
             if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
                 // In non-headless system user mode, DO can be setup only if
                 // there's no non-system user
                 if (!mInjector.userManagerIsHeadlessSystemUserMode()
-                        && !mInjector.userManagerIsSplitSystemUser()
                         && mUserManager.getUserCount() > 1) {
                     return CODE_NONSYSTEM_USER_EXISTS;
                 }
@@ -12801,16 +12755,13 @@
             }
             return CODE_OK;
         } else {
-            if (!mInjector.userManagerIsSplitSystemUser()) {
-                // In non-split user mode, DO has to be user 0
-                if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
-                    return CODE_NOT_SYSTEM_USER;
-                }
-                // Only provision DO before setup wizard completes
-                // TODO (b/171423186): implement deferred DO setup for headless system user mode
-                if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                    return CODE_USER_SETUP_COMPLETED;
-                }
+            // DO has to be user 0
+            if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+                return CODE_NOT_SYSTEM_USER;
+            }
+            // Only provision DO before setup wizard completes
+            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+                return CODE_USER_SETUP_COMPLETED;
             }
             return CODE_OK;
         }
@@ -12831,17 +12782,11 @@
         }
     }
 
-    // TODO (b/137101239): clean up split system user codes
     private int checkManagedProfileProvisioningPreCondition(String packageName,
             @UserIdInt int callingUserId) {
         if (!hasFeatureManagedUsers()) {
             return CODE_MANAGED_USERS_NOT_SUPPORTED;
         }
-        if (callingUserId == UserHandle.USER_SYSTEM
-                && mInjector.userManagerIsSplitSystemUser()) {
-            // Managed-profiles cannot be setup on the system user.
-            return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
-        }
         if (getProfileOwnerAsUser(callingUserId) != null) {
             // Managed user cannot have a managed profile.
             return CODE_USER_HAS_PROFILE_OWNER;
@@ -12917,37 +12862,6 @@
         return null;
     }
 
-    // TODO (b/137101239): clean up split system user codes
-    private int checkManagedUserProvisioningPreCondition(int callingUserId) {
-        if (!hasFeatureManagedUsers()) {
-            return CODE_MANAGED_USERS_NOT_SUPPORTED;
-        }
-        if (!mInjector.userManagerIsSplitSystemUser()) {
-            // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
-            return CODE_NOT_SYSTEM_USER_SPLIT;
-        }
-        if (callingUserId == UserHandle.USER_SYSTEM) {
-            // System user cannot be a managed user.
-            return CODE_SYSTEM_USER;
-        }
-        if (hasUserSetupCompleted(callingUserId)) {
-            return CODE_USER_SETUP_COMPLETED;
-        }
-        if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
-            return CODE_HAS_PAIRED;
-        }
-        return CODE_OK;
-    }
-
-    // TODO (b/137101239): clean up split system user codes
-    private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
-        if (!mInjector.userManagerIsSplitSystemUser()) {
-            // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
-            return CODE_NOT_SYSTEM_USER_SPLIT;
-        }
-        return checkDeviceOwnerProvisioningPreCondition(callingUserId);
-    }
-
     private boolean hasFeatureManagedUsers() {
         try {
             return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5ced868..636be4a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -342,6 +342,8 @@
             "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
     private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
             "com.android.server.searchui.SearchUiManagerService";
+    private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
+            "com.android.server.smartspace.SmartspaceManagerService";
     private static final String DEVICE_IDLE_CONTROLLER_CLASS =
             "com.android.server.DeviceIdleController";
     private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -365,7 +367,7 @@
 
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
     private static final String GAME_MANAGER_SERVICE_CLASS =
-            "com.android.server.graphics.GameManagerService$Lifecycle";
+            "com.android.server.app.GameManagerService$Lifecycle";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -1342,6 +1344,13 @@
             mSystemServiceManager.startService(DropBoxManagerService.class);
             t.traceEnd();
 
+            // Grants default permissions and defines roles
+            t.traceBegin("StartRoleManagerService");
+            LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
+                    new RoleServicePlatformHelperImpl(mSystemContext));
+            mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartVibratorManagerService");
             mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
             t.traceEnd();
@@ -1676,6 +1685,12 @@
             mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
+            // Smartspace manager service
+            // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
+            t.traceBegin("StartSmartspaceService");
+            mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("InitConnectivityModuleConnector");
             try {
                 ConnectivityModuleConnector.getInstance().init(context);
@@ -2052,13 +2067,6 @@
                 t.traceEnd();
             }
 
-            // Grants default permissions and defines roles
-            t.traceBegin("StartRoleManagerService");
-            LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
-                    new RoleServicePlatformHelperImpl(mSystemContext));
-            mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
-            t.traceEnd();
-
             // We need to always start this service, regardless of whether the
             // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
             // of initializing various settings.  It will internally modify its behavior
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 9a9a171..444f9c6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -264,7 +264,8 @@
         return new ConversationChannel(shortcutInfo, uid, parentChannel,
                 parentChannelGroup,
                 conversationInfo.getLastEventTimestamp(),
-                hasActiveNotifications(packageName, userId, shortcutId));
+                hasActiveNotifications(packageName, userId, shortcutId), false,
+                getStatuses(conversationInfo));
     }
 
     /** Returns the cached non-customized recent conversations. */
@@ -404,6 +405,10 @@
             String conversationId) {
         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
         ConversationInfo conversationInfo = getConversationInfoOrThrow(cs, conversationId);
+        return getStatuses(conversationInfo);
+    }
+
+    private @NonNull List<ConversationStatus> getStatuses(ConversationInfo conversationInfo) {
         Collection<ConversationStatus> statuses = conversationInfo.getStatuses();
         if (statuses != null) {
             final ArrayList<ConversationStatus> list = new ArrayList<>(statuses.size());
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.smartspace-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.smartspace",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.smartspace-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/services/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+        AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+                ISmartspaceService> {
+
+    private static final String TAG = "RemoteSmartspaceService";
+
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+    private final RemoteSmartspaceServiceCallbacks mCallback;
+
+    public RemoteSmartspaceService(Context context, String serviceInterface,
+            ComponentName componentName, int userId,
+            RemoteSmartspaceServiceCallbacks callback, boolean bindInstantServiceAllowed,
+            boolean verbose) {
+        super(context, serviceInterface, componentName, userId, callback,
+                context.getMainThreadHandler(),
+                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+                verbose, /* initialCapacity= */ 1);
+        mCallback = callback;
+    }
+
+    @Override
+    protected ISmartspaceService getServiceInterface(IBinder service) {
+        return ISmartspaceService.Stub.asInterface(service);
+    }
+
+    @Override
+    protected long getTimeoutIdleBindMillis() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+
+    @Override
+    protected long getRemoteRequestMillis() {
+        return TIMEOUT_REMOTE_REQUEST_MILLIS;
+    }
+
+    /**
+     * Schedules a request to bind to the remote service.
+     */
+    public void reconnect() {
+        super.scheduleBind();
+    }
+
+    /**
+     * Schedule async request on remote service.
+     */
+    public void scheduleOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+        scheduleAsyncRequest(request);
+    }
+
+    /**
+     * Execute async request on remote service immediately instead of sending it to Handler queue.
+     */
+    public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+        executeAsyncRequest(request);
+    }
+
+    /**
+     * Failure callback
+     */
+    public interface RemoteSmartspaceServiceCallbacks
+            extends VultureCallback<RemoteSmartspaceService> {
+
+        /**
+         * Notifies a the failure or timeout of a remote call.
+         */
+        void onFailureOrTimeout(boolean timedOut);
+
+        /**
+         * Notifies change in connected state of the remote service.
+         */
+        void onConnectedStateChanged(boolean connected);
+    }
+
+    @Override // from AbstractRemoteService
+    protected void handleOnConnectedStateChanged(boolean connected) {
+        if (mCallback != null) {
+            mCallback.onConnectedStateChanged(connected);
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85eb
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+        AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+    private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    public SmartspaceManagerService(Context context) {
+        super(context, new FrameworkResourcesServiceNameResolver(context,
+                        com.android.internal.R.string.config_defaultSmartspaceService), null,
+                PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+        return new SmartspacePerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+        final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageUpdatedLocked();
+        }
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageRestartedLocked();
+        }
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
+    private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+        @Override
+        public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+                @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+            runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+                    service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+        }
+
+        @Override
+        public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+                SmartspaceTargetEvent event) {
+            runForUserLocked("notifySmartspaceEvent", sessionId,
+                    (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+        }
+
+        @Override
+        public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+            runForUserLocked("requestSmartspaceUpdate", sessionId,
+                    (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+        }
+
+        @Override
+        public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+                @NonNull ISmartspaceCallback callback) {
+            runForUserLocked("registerSmartspaceUpdates", sessionId,
+                    (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+        }
+
+        @Override
+        public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+                    (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+        }
+
+        @Override
+        public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+            runForUserLocked("destroySmartspaceSession", sessionId,
+                    (service) -> service.onDestroyLocked(sessionId));
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) {
+            new SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        private void runForUserLocked(@NonNull final String func,
+                @NonNull final SmartspaceSessionId sessionId,
+                @NonNull final Consumer<SmartspacePerUserService> c) {
+            ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+            final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                    sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
+
+            if (DEBUG) {
+                Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+            }
+            if (!(mServiceNameResolver.isTemporary(userId)
+                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+                String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid();
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final SmartspacePerUserService service = getServiceForUserLocked(userId);
+                    c.accept(service);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+    private static final String TAG =
+            SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+    private final SmartspaceManagerService mService;
+
+    public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-service": {
+                        final int userId = Integer.parseInt(getNextArgRequired());
+                        String serviceName = getNextArg();
+                        if (serviceName == null) {
+                            mService.resetTemporaryService(userId);
+                            pw.println("SmartspaceService temporarily reset. ");
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryService(userId, serviceName, duration);
+                        pw.println("SmartspaceService temporarily set to " + serviceName
+                                + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("SmartspaceManagerService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+        AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+        implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+    private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+    @GuardedBy("mLock")
+    private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+            new ArrayMap<>();
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteSmartspaceService mRemoteService;
+    /**
+     * When {@code true}, remote service died but service state is kept so it's restored after
+     * the system re-binds to it.
+     */
+    @GuardedBy("mLock")
+    private boolean mZombie;
+
+    protected SmartspacePerUserService(SmartspaceManagerService master,
+            Object lock, int userId) {
+        super(master, lock, userId);
+    }
+
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws NameNotFoundException {
+
+        ServiceInfo si;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new NameNotFoundException("Could not get service for " + serviceComponent);
+        }
+        // TODO(b/177858728): must check that either the service is from a system component,
+        // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+        // OEMs are implementing the real service and also verify the proper permissions
+        return si;
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        if (enabledChanged) {
+            if (!isEnabledLocked()) {
+                // Clear the remote service for the next call
+                updateRemoteServiceLocked();
+            }
+        }
+        return enabledChanged;
+    }
+
+    /**
+     * Notifies the service of a new smartspace session.
+     */
+    @GuardedBy("mLock")
+    public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+            @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+        if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+            final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+                    sessionId, smartspaceConfig, token, () -> {
+                synchronized (mLock) {
+                    onDestroyLocked(sessionId);
+                }
+            });
+            if (sessionInfo.linkToDeath()) {
+                mSessionInfos.put(sessionId, sessionInfo);
+            } else {
+                // destroy the session if calling process is already dead
+                onDestroyLocked(sessionId);
+            }
+        }
+    }
+
+    /**
+     * Records an smartspace event to the service.
+     */
+    @GuardedBy("mLock")
+    public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull SmartspaceTargetEvent event) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+    }
+
+    /**
+     * Requests the service to return smartspace results of an input query.
+     */
+    @GuardedBy("mLock")
+    public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId,
+                s -> s.requestSmartspaceUpdate(sessionId));
+    }
+
+    /**
+     * Registers a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.registerSmartspaceUpdates(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.addCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.removeCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Notifies the service of the end of an existing smartspace session.
+     */
+    @GuardedBy("mLock")
+    public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+        if (isDebug()) {
+            Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+        }
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
+        sessionInfo.destroy();
+    }
+
+    @Override
+    public void onFailureOrTimeout(boolean timedOut) {
+        if (isDebug()) {
+            Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+        }
+        // Do nothing, we are just proxying to the smartspace ui service
+    }
+
+    @Override
+    public void onConnectedStateChanged(boolean connected) {
+        if (isDebug()) {
+            Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+        }
+        if (connected) {
+            synchronized (mLock) {
+                if (mZombie) {
+                    // Validation check - shouldn't happen
+                    if (mRemoteService == null) {
+                        Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+                        return;
+                    }
+                    mZombie = false;
+                    resurrectSessionsLocked();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDied(RemoteSmartspaceService service) {
+        if (isDebug()) {
+            Slog.w(TAG, "onServiceDied(): service=" + service);
+        }
+        synchronized (mLock) {
+            mZombie = true;
+        }
+        updateRemoteServiceLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            mRemoteService.destroy();
+            mRemoteService = null;
+        }
+    }
+
+    void onPackageUpdatedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageUpdatedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    void onPackageRestartedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageRestartedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    private void destroyAndRebindRemoteService() {
+        if (mRemoteService == null) {
+            return;
+        }
+
+        if (isDebug()) {
+            Slog.d(TAG, "Destroying the old remote service.");
+        }
+        mRemoteService.destroy();
+        mRemoteService = null;
+
+        synchronized (mLock) {
+            mZombie = true;
+        }
+        mRemoteService = getRemoteServiceLocked();
+        if (mRemoteService != null) {
+            if (isDebug()) {
+                Slog.d(TAG, "Rebinding to the new remote service.");
+            }
+            mRemoteService.reconnect();
+        }
+    }
+
+    /**
+     * Called after the remote service connected, it's used to restore state from a 'zombie'
+     * service (i.e., after it died).
+     */
+    private void resurrectSessionsLocked() {
+        final int numSessions = mSessionInfos.size();
+        if (isDebug()) {
+            Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+                    + numSessions + " sessions.");
+        }
+
+        for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+            sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    protected boolean resolveService(
+            @NonNull final SmartspaceSessionId sessionId,
+            @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+        final RemoteSmartspaceService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.executeOnResolvedService(cb);
+        }
+        return service != null;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSmartspaceService getRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteServiceLocked(): not set");
+                }
+                return null;
+            }
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+            mRemoteService = new RemoteSmartspaceService(getContext(),
+                    SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
+        return mRemoteService;
+    }
+
+    private static final class SmartspaceSessionInfo {
+        private static final boolean DEBUG = false;  // Do not submit with true
+        @NonNull
+        final IBinder mToken;
+        @NonNull
+        final IBinder.DeathRecipient mDeathRecipient;
+        @NonNull
+        private final SmartspaceSessionId mSessionId;
+        @NonNull
+        private final SmartspaceConfig mSmartspaceConfig;
+        private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+                new RemoteCallbackList<ISmartspaceCallback>() {
+                    @Override
+                    public void onCallbackDied(ISmartspaceCallback callback) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Binder died for session Id=" + mSessionId
+                                    + " and callback=" + callback.asBinder());
+                        }
+                        if (mCallbacks.getRegisteredCallbackCount() == 0) {
+                            destroy();
+                        }
+                    }
+                };
+
+        SmartspaceSessionInfo(
+                @NonNull final SmartspaceSessionId id,
+                @NonNull final SmartspaceConfig context,
+                @NonNull final IBinder token,
+                @NonNull final IBinder.DeathRecipient deathRecipient) {
+            if (DEBUG) {
+                Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+            }
+            mSessionId = id;
+            mSmartspaceConfig = context;
+            mToken = token;
+            mDeathRecipient = deathRecipient;
+        }
+
+        void addCallbackLocked(ISmartspaceCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.register(callback);
+        }
+
+        void removeCallbackLocked(ISmartspaceCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.unregister(callback);
+        }
+
+        boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+                            + mSessionId);
+                }
+                return false;
+            }
+            return true;
+        }
+
+        void destroy() {
+            if (DEBUG) {
+                Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
+                        + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
+            }
+            if (mToken != null) {
+                mToken.unlinkToDeath(mDeathRecipient, 0);
+            }
+            mCallbacks.kill();
+        }
+
+        void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
+            int callbackCount = mCallbacks.getRegisteredCallbackCount();
+            if (DEBUG) {
+                Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+                        + ") for session Id=" + mSessionId + " and "
+                        + callbackCount + " callbacks.");
+            }
+            service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index 0a35db5..f7b2492 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -16,34 +16,61 @@
 
 package com.android.server;
 
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
 import static org.junit.Assert.assertArrayEquals;
 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.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
 import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.os.CombinedVibrationEffect;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.IVibratorStateListener;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.vibrator.FakeVibrator;
 import com.android.server.vibrator.FakeVibratorControllerProvider;
 import com.android.server.vibrator.VibratorController;
 
@@ -51,12 +78,16 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Tests for {@link VibratorManagerService}.
@@ -67,39 +98,86 @@
 @Presubmit
 public class VibratorManagerServiceTest {
 
+    private static final int TEST_TIMEOUT_MILLIS = 1_000;
     private static final int UID = Process.ROOT_UID;
     private static final String PACKAGE_NAME = "package";
+    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+            .setBatterySaverEnabled(true).build();
     private static final VibrationAttributes ALARM_ATTRS =
             new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_TOUCH).build();
+    private static final VibrationAttributes NOTIFICATION_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_NOTIFICATION).build();
+    private static final VibrationAttributes RINGTONE_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_RINGTONE).build();
 
     @Rule public MockitoRule rule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
     @Mock private PowerManagerInternal mPowerManagerInternalMock;
     @Mock private PowerSaveState mPowerSaveStateMock;
+    @Mock private AppOpsManager mAppOpsManagerMock;
+    @Mock private IInputManager mIInputManagerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
+    private Context mContextSpy;
     private TestLooper mTestLooper;
+    private FakeVibrator mVibrator;
+    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
+        mVibrator = new FakeVibrator();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
 
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
+        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
+        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
         when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
                 .thenReturn(mPowerSaveStateMock);
+        doAnswer(invocation -> {
+            mRegisteredPowerModeListener = invocation.getArgument(0);
+            return null;
+        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
 
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+
+        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
         addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        mTestLooper.startAutoDispatch();
     }
 
     @After
     public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
     }
 
     private VibratorManagerService createService() {
         VibratorManagerService service = new VibratorManagerService(
-                InstrumentationRegistry.getContext(),
+                mContextSpy,
                 new VibratorManagerService.Injector() {
                     @Override
                     VibratorManagerService.NativeWrapper getNativeWrapper() {
@@ -172,6 +250,74 @@
     }
 
     @Test
+    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        // Wait until effect ends.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.unregisterVibratorStateListener(1, listenerMock);
+
+        // Wait until vibrator is off.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
+        mockVibrators(0, 1, 2);
+        VibratorManagerService service = createService();
+        IVibratorStateListener[] listeners = new IVibratorStateListener[3];
+        for (int i = 0; i < 3; i++) {
+            listeners[i] = mockVibratorStateListener();
+            service.registerVibratorStateListener(i, listeners[i]);
+        }
+
+        vibrate(service, CombinedVibrationEffect.startSynced()
+                .addVibrator(0, VibrationEffect.createOneShot(40, 100))
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine(), ALARM_ATTRS);
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
+
+        verify(listeners[0]).onVibrating(eq(true));
+        verify(listeners[1]).onVibrating(eq(true));
+        verify(listeners[2], never()).onVibrating(eq(true));
+    }
+
+    @Test
     public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
@@ -276,22 +422,264 @@
     }
 
     @Test
-    public void vibrate_isUnsupported() {
+    public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
+        mockVibrators(1);
+        mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
         VibratorManagerService service = createService();
-        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        assertExpectException(UnsupportedOperationException.class,
-                "Not implemented",
-                () -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
+        vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS);
+
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+        service = createService();
+        vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+        service = createService();
+        vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        assertEquals(2, mVibratorProviders.get(1).getEffects().size());
+        assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
     }
 
     @Test
-    public void cancelVibrate_isUnsupported() {
+    public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorManagerService service = createService();
-        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        assertExpectException(UnsupportedOperationException.class,
-                "Not implemented", () -> service.cancelVibrate(service));
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
+        vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+                service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+        vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+                service, TEST_TIMEOUT_MILLIS));
+
+        assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createService();
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+        VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
+                audioAttributes, effect).build();
+
+        vibrate(service, effect, vibrationAttributes);
+
+        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createService();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_UNKNOWN).build());
+
+        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+                anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+        VibratorManagerService service = createService();
+
+        // Prebaked vibration will play fallback waveform on input device.
+        ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+        verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
+        assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
+
+        VibrationEffect[] effects = new VibrationEffect[]{
+                VibrationEffect.createOneShot(100, 128),
+                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+        };
+
+        for (VibrationEffect effect : effects) {
+            vibrate(service, effect, ALARM_ATTRS);
+            verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
+        }
+
+        // VibrationThread will start this vibration async, so wait before checking it never played.
+        assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+                service, /* timeout= */ 50));
+    }
+
+    @Test
+    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+        // The native callback will be dispatched manually in this test.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before triggering callbacks.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Trigger callbacks from controller.
+        mTestLooper.moveTimeForward(50);
+        mTestLooper.dispatchAll();
+
+        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+
+    @Test
+    public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
+        mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+
+        vibrate(service, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine(), ALARM_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(20, 100))
+                .combine(), NOTIFICATION_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose(), HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+
+        assertEquals(3, fakeVibrator.getEffects().size());
+        assertEquals(1, fakeVibrator.getAmplitudes().size());
+
+        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
+        VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
+                VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertEquals(expected, fakeVibrator.getEffects().get(0));
+
+        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
+        assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+
+        // Haptic feedback vibrations will be scaled with SCALE_LOW.
+        VibrationEffect.Composed played =
+                (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
+        assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
+        assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+
+        // Ring vibrations have intensity OFF and are not played.
+    }
+
+    @Test
+    public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
+        mockVibrators(1, 2);
+        VibratorManagerService service = createService();
+        vibrate(service,
+                CombinedVibrationEffect.startSynced()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback cancelled on low power mode.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+
+        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.updateServiceState();
+        // Vibration is not stopped nearly after updating service.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+    }
+
+    @Test
+    public void cancelVibrate_stopsVibrating() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+
+        service.cancelVibrate(service);
+        assertFalse(service.isVibrating(1));
+
+        vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.cancelVibrate(service);
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
     }
 
     private void mockVibrators(int... vibratorIds) {
@@ -302,8 +690,56 @@
         when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
     }
 
+    private IVibratorStateListener mockVibratorStateListener() {
+        IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
+        IBinder binderMock = mock(IBinder.class);
+        when(listenerMock.asBinder()).thenReturn(binderMock);
+        return listenerMock;
+    }
+
+    private InputDevice createInputDeviceWithVibrator(int id) {
+        return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
+                null, /* hasVibrator= */ true, false, false, false, false);
+    }
+
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
         LocalServices.removeServiceForTest(clazz);
         LocalServices.addService(clazz, mock);
     }
+
+    private void setRingerMode(int ringerMode) {
+        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
+        audioManager.setRingerModeInternal(ringerMode);
+        assertEquals(ringerMode, audioManager.getRingerModeInternal());
+    }
+
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+    }
+
+    private void setGlobalSetting(String settingName, int value) {
+        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
+    }
+
+    private void vibrate(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) {
+        vibrate(service, CombinedVibrationEffect.createSynced(effect), attrs);
+    }
+
+    private void vibrate(VibratorManagerService service, CombinedVibrationEffect effect,
+            VibrationAttributes attrs) {
+        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+    }
+
+    private boolean waitUntil(Predicate<VibratorManagerService> predicate,
+            VibratorManagerService service, long timeout) throws InterruptedException {
+        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+        boolean predicateResult = false;
+        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+            Thread.sleep(10);
+            predicateResult = predicate.test(service);
+        }
+        return predicateResult;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 743848c..2a7905a 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -582,16 +582,19 @@
         VibratorService service = createService();
 
         service.registerVibratorStateListener(mVibratorStateListenerMock);
-        verify(mVibratorStateListenerMock).onVibrating(false);
 
-        vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
+        vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
 
         // VibrationThread will start this vibration async, so wait before triggering callbacks.
         Thread.sleep(10);
+        assertTrue(service.isVibrating());
+
         service.unregisterVibratorStateListener(mVibratorStateListenerMock);
         // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(150);
+        mTestLooper.moveTimeForward(50);
         mTestLooper.dispatchAll();
+        Thread.sleep(20);
+        assertFalse(service.isVibrating());
 
         InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
         // First notification done when listener is registered.
@@ -745,7 +748,8 @@
 
     private InputDevice createInputDeviceWithVibrator(int id) {
         return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, /* hasVibrator= */ true, false, false, false /* hasSensor */);
+                null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
+                false /* hasBattery */);
     }
 
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
new file mode 100644
index 0000000..e44b0836
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.power.PowerStatsInternal;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.power.MeasuredEnergyArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Tests for {@link BatteryExternalStatsWorker}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
+ */
+public class BatteryExternalStatsWorkerTest {
+    private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
+    private TestBatteryStatsImpl mBatteryStatsImpl;
+    private TestPowerStatsInternal mPowerStatsInternal;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getContext();
+
+        mBatteryStatsImpl = new TestBatteryStatsImpl();
+        mPowerStatsInternal = new TestPowerStatsInternal();
+        mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context),
+                mBatteryStatsImpl);
+    }
+
+    @Test
+    public void getEnergyConsumptionData() {
+        SparseLongArray expectSubsystems = new SparseLongArray();
+        // Add some energy consumers used by BatteryExternalStatsWorker.
+        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
+                "display");
+        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
+
+        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
+        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
+        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
+        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
+
+        // Inform BESW that PowerStatsInternal is ready to query
+        mBatteryExternalStatsWorker.systemServicesReady();
+
+        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
+
+        assertEquals(expectSubsystems.size(), energies.size());
+        final int size = expectSubsystems.size();
+
+        for (int i = 0; i < size; i++) {
+            int subsystem = expectSubsystems.keyAt(i);
+            // find the subsystem in the returned MeasuredEnergyArray
+            int subsystemIndex = -1;
+            for (int j = 0; j < size; j++) {
+                if (subsystem == energies.getSubsystem(i)) {
+                    subsystemIndex = i;
+                    break;
+                }
+            }
+            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
+                    subsystemIndex);
+            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
+        }
+    }
+
+    public class TestInjector extends BatteryExternalStatsWorker.Injector {
+        public TestInjector(Context context) {
+            super(context);
+        }
+
+        public <T> T getSystemService(Class<T> serviceClass) {
+            return null;
+        }
+
+        public <T> T getLocalService(Class<T> serviceClass) {
+            if (serviceClass == PowerStatsInternal.class) {
+                return (T) mPowerStatsInternal;
+            }
+            return null;
+        }
+    }
+
+    public class TestBatteryStatsImpl extends BatteryStatsImpl {
+    }
+
+    public class TestPowerStatsInternal extends PowerStatsInternal {
+        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
+        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
+                new SparseArray<>();
+        private final int mTimeSinceBoot = 0;
+
+        @Override
+        public EnergyConsumer[] getEnergyConsumerInfo() {
+            final int size = mEnergyConsumers.size();
+            final EnergyConsumer[] consumers = new EnergyConsumer[size];
+            for (int i = 0; i < size; i++) {
+                consumers[i] = mEnergyConsumers.valueAt(i);
+            }
+            return consumers;
+        }
+
+        @Override
+        public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
+                int[] energyConsumerIds) {
+            final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
+            final int size = mEnergyConsumerResults.size();
+            final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
+            for (int i = 0; i < size; i++) {
+                results[i] = mEnergyConsumerResults.valueAt(i);
+            }
+            future.complete(results);
+            return future;
+        }
+
+        /**
+         * Util method to add a new EnergyConsumer for testing
+         *
+         * @return the EnergyConsumer id of the new EnergyConsumer
+         */
+        public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) {
+            final EnergyConsumer consumer = new EnergyConsumer();
+            final int id = getNextAvailableId();
+            consumer.id = id;
+            consumer.type = type;
+            consumer.ordinal = ordinal;
+            consumer.name = name;
+            mEnergyConsumers.put(id, consumer);
+
+            final EnergyConsumerResult result = new EnergyConsumerResult();
+            result.id = id;
+            result.timestampMs = mTimeSinceBoot;
+            result.energyUWs = 0;
+            mEnergyConsumerResults.put(id, result);
+            return id;
+        }
+
+        public void incrementEnergyConsumption(int id, long energyUWs) {
+            EnergyConsumerResult result = mEnergyConsumerResults.get(id, null);
+            assertNotNull(result);
+            result.energyUWs += energyUWs;
+        }
+
+        private int getNextAvailableId() {
+            final int size = mEnergyConsumers.size();
+            // Just return the first index that does not match the key (aka the EnergyConsumer id)
+            for (int i = size - 1; i >= 0; i--) {
+                if (mEnergyConsumers.keyAt(i) == i) return i + 1;
+            }
+            // Otherwise return the lowest id
+            return 0;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 6fc6b9e..738f008 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.graphics;
+package com.android.server.app;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index 22df645..caa46da 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.graphics;
+package com.android.server.app;
 
 import static org.junit.Assert.assertEquals;
 
-import android.graphics.GameManager;
+import android.app.GameManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
new file mode 100644
index 0000000..275e7c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.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 com.android.server.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontCrashDetectorTest {
+
+    private File mCacheDir;
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
+        FileUtils.deleteContentsAndDir(mCacheDir);
+        mCacheDir.mkdirs();
+    }
+
+    @Test
+    public void detectCrash() throws Exception {
+        // Prepare a marker file.
+        File file = new File(mCacheDir, "detectCrash");
+        assertThat(file.createNewFile()).isTrue();
+
+        FontCrashDetector detector = new FontCrashDetector(file);
+        assertThat(detector.hasCrashed()).isTrue();
+
+        detector.clear();
+        assertThat(detector.hasCrashed()).isFalse();
+        assertThat(file.exists()).isFalse();
+    }
+
+    @Test
+    public void monitorCrash() {
+        File file = new File(mCacheDir, "monitorCrash");
+        FontCrashDetector detector = new FontCrashDetector(file);
+        assertThat(detector.hasCrashed()).isFalse();
+
+        FontCrashDetector.MonitoredBlock block = detector.start();
+        assertThat(file.exists()).isTrue();
+
+        block.close();
+        assertThat(file.exists()).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index d067790..8331031 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -147,6 +147,7 @@
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dirForPreparation.loadFontFileMap();
         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
                 .isEqualTo(expectedModifiedDate);
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
@@ -162,6 +163,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
         assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -177,6 +179,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
@@ -187,6 +190,7 @@
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dirForPreparation.loadFontFileMap();
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -199,6 +203,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -211,6 +216,7 @@
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dirForPreparation.loadFontFileMap();
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -224,6 +230,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -236,6 +243,7 @@
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dirForPreparation.loadFontFileMap();
         installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
         installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -250,6 +258,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
         // For foo.ttf, preinstalled font (revision 5) should be used.
         assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
         // For bar.ttf, updated font (revision 4) should be used.
@@ -268,6 +277,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 new File("/dev/null"));
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
@@ -278,6 +288,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         installFontFile(dir, "test,1", GOOD_SIGNATURE);
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
@@ -295,6 +306,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         installFontFile(dir, "test,1", GOOD_SIGNATURE);
         Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
@@ -313,6 +325,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         installFontFile(dir, "test,2", GOOD_SIGNATURE);
         try {
@@ -333,6 +346,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         installFontFile(dir, "foo,1", GOOD_SIGNATURE);
         installFontFile(dir, "bar,2", GOOD_SIGNATURE);
@@ -349,6 +363,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         try {
             installFontFile(dir, "test,1", "Invalid signature");
@@ -368,6 +383,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         try {
             installFontFile(dir, "test,1", GOOD_SIGNATURE);
@@ -398,6 +414,7 @@
             UpdatableFontDir dir = new UpdatableFontDir(
                     mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                     readonlyFile);
+            dir.loadFontFileMap();
 
             try {
                 installFontFile(dir, "test,2", GOOD_SIGNATURE);
@@ -429,6 +446,7 @@
                         return 0;
                     }
                 }, fakeFsverityUtil, mConfigFile);
+        dir.loadFontFileMap();
 
         try {
             installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -456,6 +474,7 @@
                         return 0;
                     }
                 }, fakeFsverityUtil, mConfigFile);
+        dir.loadFontFileMap();
 
         try {
             installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -491,6 +510,7 @@
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
+        dir.loadFontFileMap();
 
         try {
             installFontFile(dir, "foo,1", GOOD_SIGNATURE);
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 15a9bcf..6208801 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -21,9 +21,11 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.util.Log;
+import android.annotation.NonNull;
 import android.util.Pair;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -58,106 +60,112 @@
      * Represents running and pending jobs.
      */
     class Jobs {
-        public int runningFg;
-        public int runningBg;
-        public int pendingFg;
-        public int pendingBg;
+        public final SparseIntArray running = new SparseIntArray();
+        public final SparseIntArray pending = new SparseIntArray();
 
         public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
             while (mRandom.nextDouble() < startRatio) {
                 if (mRandom.nextDouble() < fgJobRatio) {
-                    pendingFg++;
+                    pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
                 } else {
-                    pendingBg++;
+                    pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
                 }
             }
         }
 
         public void maybeFinishJobs(double stopRatio) {
-            for (int i = runningBg; i > 0; i--) {
+            for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
                 if (mRandom.nextDouble() < stopRatio) {
-                    runningBg--;
+                    running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
                 }
             }
-            for (int i = runningFg; i > 0; i--) {
+            for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
                 if (mRandom.nextDouble() < stopRatio) {
-                    runningFg--;
+                    running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
                 }
             }
         }
     }
 
 
-    private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
-        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
-                totalMax,
-                // defaultMin
-                List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
-                        Pair.create(WORK_TYPE_BG, minBg)),
-                // defaultMax
-                List.of(Pair.create(WORK_TYPE_BG, maxBg))));
+    private void startPendingJobs(Jobs jobs, int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits) {
+        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig(
+                "test", totalMax, minLimits, maxLimits));
         mWorkCountTracker.resetCounts();
 
-        for (int i = 0; i < jobs.runningFg; i++) {
-            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
-        }
-        for (int i = 0; i < jobs.runningBg; i++) {
-            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
-        }
+        for (int i = 0; i < jobs.running.size(); ++i) {
+            final int workType = jobs.running.keyAt(i);
+            final int count = jobs.running.valueAt(i);
 
-        for (int i = 0; i < jobs.pendingFg; i++) {
-            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
+            for (int c = 0; c < count; ++c) {
+                mWorkCountTracker.incrementRunningJobCount(workType);
+            }
         }
-        for (int i = 0; i < jobs.pendingBg; i++) {
-            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
+        for (int i = 0; i < jobs.pending.size(); ++i) {
+            final int workType = jobs.pending.keyAt(i);
+            final int count = jobs.pending.valueAt(i);
+
+            for (int c = 0; c < count; ++c) {
+                mWorkCountTracker.incrementPendingJobCount(workType);
+            }
         }
 
         mWorkCountTracker.onCountDone();
 
-        while ((jobs.pendingFg > 0
+        while ((jobs.pending.get(WORK_TYPE_TOP) > 0
                 && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
-                || (jobs.pendingBg > 0
+                || (jobs.pending.get(WORK_TYPE_BG) > 0
                 && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
             final boolean isStartingFg = mRandom.nextBoolean();
 
             if (isStartingFg) {
-                if (jobs.pendingFg > 0
+                if (jobs.pending.get(WORK_TYPE_TOP) > 0
                         && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
-                    jobs.pendingFg--;
-                    jobs.runningFg++;
+                    jobs.pending.put(WORK_TYPE_TOP, jobs.pending.get(WORK_TYPE_TOP) - 1);
+                    jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) + 1);
                     mWorkCountTracker.stageJob(WORK_TYPE_TOP);
                     mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
                 }
             } else {
-                if (jobs.pendingBg > 0
+                if (jobs.pending.get(WORK_TYPE_BG) > 0
                         && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
-                    jobs.pendingBg--;
-                    jobs.runningBg++;
+                    jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
+                    jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) + 1);
                     mWorkCountTracker.stageJob(WORK_TYPE_BG);
                     mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
                 }
             }
         }
-
-        Log.i(TAG, "" + mWorkCountTracker);
     }
 
     /**
      * Used by the following testRandom* tests.
      */
-    private void checkRandom(Jobs jobs, int numTests, int totalMax, int maxBg, int minBg,
+    private void checkRandom(Jobs jobs, int numTests, int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits,
             double startRatio, double fgJobRatio, double stopRatio) {
         for (int i = 0; i < numTests; i++) {
-
             jobs.maybeFinishJobs(stopRatio);
             jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
 
-            startPendingJobs(jobs, totalMax, maxBg, minBg);
+            startPendingJobs(jobs, totalMax, minLimits, maxLimits);
 
-            assertThat(jobs.runningFg).isAtMost(totalMax);
-            assertThat(jobs.runningBg).isAtMost(totalMax);
-            assertThat(jobs.runningFg + jobs.runningBg).isAtMost(totalMax);
-            assertThat(jobs.runningBg).isAtMost(maxBg);
+            int totalRunning = 0;
+            for (int r = 0; r < jobs.running.size(); ++r) {
+                final int numRunning = jobs.running.valueAt(r);
+                assertWithMessage(
+                        "Work type " + jobs.running.keyAt(r) + " is running too many jobs")
+                        .that(numRunning).isAtMost(totalMax);
+                totalRunning += numRunning;
+            }
+            assertThat(totalRunning).isAtMost(totalMax);
+            for (Pair<Integer, Integer> maxLimit : maxLimits) {
+                assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
+                        .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
+            }
         }
     }
 
@@ -170,13 +178,14 @@
 
         final int numTests = 5000;
         final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.1;
         final double fgJobRatio = 0.5;
         final double startRatio = 0.1;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio , stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -185,13 +194,14 @@
 
         final int numTests = 5000;
         final int totalMax = 2;
-        final int maxBg = 2;
-        final int minBg = 0;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of();
         final double stopRatio = 0.5;
         final double fgJobRatio = 0.5;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -200,13 +210,14 @@
 
         final int numTests = 5000;
         final int totalMax = 2;
-        final int maxBg = 2;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.5;
         final double fgJobRatio = 0.5;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -215,13 +226,14 @@
 
         final int numTests = 5000;
         final int totalMax = 10;
-        final int maxBg = 2;
-        final int minBg = 0;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of();
         final double stopRatio = 0.5;
         final double fgJobRatio = 0.5;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -230,13 +242,14 @@
 
         final int numTests = 5000;
         final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.5;
         final double fgJobRatio = 0.1;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -245,13 +258,14 @@
 
         final int numTests = 5000;
         final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.5;
         final double fgJobRatio = 0.9;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -260,13 +274,14 @@
 
         final int numTests = 5000;
         final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.4;
         final double fgJobRatio = 0.1;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     @Test
@@ -275,53 +290,135 @@
 
         final int numTests = 5000;
         final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double stopRatio = 0.4;
         final double fgJobRatio = 0.9;
         final double startRatio = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
     }
 
     /** Used by the following tests */
-    private void checkSimple(int totalMax, int maxBg, int minBg,
-            int runningFg, int runningBg, int pendingFg, int pendingBg,
-            int resultRunningFg, int resultRunningBg, int resultPendingFg, int resultPendingBg) {
+    private void checkSimple(int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits,
+            @NonNull List<Pair<Integer, Integer>> running,
+            @NonNull List<Pair<Integer, Integer>> pending,
+            @NonNull List<Pair<Integer, Integer>> resultRunning,
+            @NonNull List<Pair<Integer, Integer>> resultPending) {
         final Jobs jobs = new Jobs();
-        jobs.runningFg = runningFg;
-        jobs.runningBg = runningBg;
-        jobs.pendingFg = pendingFg;
-        jobs.pendingBg = pendingBg;
+        for (Pair<Integer, Integer> run : running) {
+            jobs.running.put(run.first, run.second);
+        }
+        for (Pair<Integer, Integer> pend : pending) {
+            jobs.pending.put(pend.first, pend.second);
+        }
 
-        startPendingJobs(jobs, totalMax, maxBg, minBg);
+        startPendingJobs(jobs, totalMax, minLimits, maxLimits);
 
-//        fail(mWorkerCountTracker.toString());
-        assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
-        assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
-
-        assertThat(jobs.pendingFg).isEqualTo(resultPendingFg);
-        assertThat(jobs.pendingBg).isEqualTo(resultPendingBg);
+        for (Pair<Integer, Integer> run : resultRunning) {
+            assertWithMessage("Incorrect running result for work type " + run.first)
+                    .that(jobs.running.get(run.first)).isEqualTo(run.second);
+        }
+        for (Pair<Integer, Integer> pend : resultPending) {
+            assertWithMessage("Incorrect pending result for work type " + pend.first)
+                    .that(jobs.pending.get(pend.first)).isEqualTo(pend.second);
+        }
     }
 
-
     @Test
     public void testBasic() {
-        // Args are:
-        // First 3: Total-max, bg-max, bg-min.
-        // Next 2:  Running FG / BG
-        // Next 2:  Pending FG / BG
-        // Next 4:  Result running FG / BG, pending FG/BG.
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 1, 0, /*res run/pen=*/ 1, 0, 0, 0);
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+                /* resPen */ List.of());
 
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 0, /*res run/pen=*/ 6, 0, 4, 0);
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 4)));
 
         // When there are BG jobs pending, 2 (min-BG) jobs should run.
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 5)));
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 1)));
 
-        checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 43)));
 
-        checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 1)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 48)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+
+        // This could happen if we lower the effective config due to higher memory pressure after
+        // we've already started running jobs. We shouldn't stop already running jobs, but also
+        // shouldn't start new ones.
+        checkSimple(5,
+                /* min */ List.of(),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+                /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index fba36cb..c28292f 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -18,6 +18,9 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
 
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.provider.DeviceConfig;
 import android.util.Pair;
@@ -32,6 +35,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -58,42 +62,90 @@
     }
 
     private void check(@Nullable DeviceConfig.Properties config,
-            int defaultTotal, int defaultMaxBg, int defaultMinBg,
-            int expectedTotal, int expectedMaxBg, int expectedMinBg) throws Exception {
+            int defaultTotal,
+            @Nullable Pair<Integer, Integer> defaultTopLimits,
+            @Nullable Pair<Integer, Integer> defaultBgLimits,
+            boolean expectedValid, int expectedTotal,
+            @NonNull Pair<Integer, Integer> expectedTopLimits,
+            @NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception {
         resetConfig();
         if (config != null) {
             DeviceConfig.setProperties(config);
         }
 
-        final WorkTypeConfig counts = new WorkTypeConfig("test",
-                defaultTotal,
-                // defaultMin
-                List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg),
-                        Pair.create(WORK_TYPE_BG, defaultMinBg)),
-                // defaultMax
-                List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg)));
+        List<Pair<Integer, Integer>> defaultMin = new ArrayList<>();
+        List<Pair<Integer, Integer>> defaultMax = new ArrayList<>();
+        Integer val;
+        if (defaultTopLimits != null) {
+            if ((val = defaultTopLimits.first) != null) {
+                defaultMin.add(Pair.create(WORK_TYPE_TOP, val));
+            }
+            if ((val = defaultTopLimits.second) != null) {
+                defaultMax.add(Pair.create(WORK_TYPE_TOP, val));
+            }
+        }
+        if (defaultBgLimits != null) {
+            if ((val = defaultBgLimits.first) != null) {
+                defaultMin.add(Pair.create(WORK_TYPE_BG, val));
+            }
+            if ((val = defaultBgLimits.second) != null) {
+                defaultMax.add(Pair.create(WORK_TYPE_BG, val));
+            }
+        }
+
+        final WorkTypeConfig counts;
+        try {
+            counts = new WorkTypeConfig("test",
+                    defaultTotal, defaultMin, defaultMax);
+            if (!expectedValid) {
+                fail("Invalid config successfully created");
+                return;
+            }
+        } catch (IllegalArgumentException e) {
+            if (expectedValid) {
+                throw e;
+            } else {
+                // Success
+                return;
+            }
+        }
 
         counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
 
         Assert.assertEquals(expectedTotal, counts.getMaxTotal());
-        Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
-        Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
+        Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
+        Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
+        Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
+        Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
     }
 
     @Test
     public void test() throws Exception {
         // Tests with various combinations.
-        check(null, /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
-        check(null, /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
-        check(null, /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
-        check(null, /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
-        check(null, /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
-        check(null, /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
-        check(null, /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
-        check(null, /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
-        check(null, /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
-        check(null, /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
-        check(null, /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+        check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1),
+                /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1));
+        check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0),
+                /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1));
+        check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0),
+                /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+        check(null, /*default*/ -1, null, Pair.create(-1, -1),
+                /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+        check(null, /*default*/ 5, null, Pair.create(5, 5),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+        check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5),
+                /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5));
+        check(null, /*default*/ 4, null, Pair.create(6, 5),
+                /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4));
+        check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1),
+                /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1));
+        check(null, /*default*/ 15, null, Pair.create(15, 15),
+                /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15));
+        check(null, /*default*/ 16, null, Pair.create(16, 16),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+        check(null, /*default*/ 20, null, Pair.create(20, 20),
+                /*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16));
+        check(null, /*default*/ 20, null, Pair.create(16, 16),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
 
         // Test for overriding with a setting string.
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
@@ -101,15 +153,26 @@
                         .setInt(KEY_MAX_BG, 4)
                         .setInt(KEY_MIN_BG, 3)
                         .build(),
-                /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4));
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
                         .setInt(KEY_MAX_TOTAL, 5).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
                         .setInt(KEY_MAX_BG, 4).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4));
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
                         .setInt(KEY_MIN_BG, 3).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9));
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_TOTAL, 20)
+                        .setInt(KEY_MAX_BG, 20)
+                        .setInt(KEY_MIN_BG, 8)
+                        .build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 161d316..6e57896 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -535,8 +535,11 @@
         listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
                 mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
 
-        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
-                TEST_SHORTCUT_ID)).isNotNull();
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertThat(result).isNotNull();
+        assertThat(result.hasBirthdayToday()).isFalse();
+        assertThat(result.getStatuses()).isEmpty();
     }
 
     @Test
@@ -550,13 +553,15 @@
         shortcut.setCached(ShortcutInfo.FLAG_PINNED);
         mDataManager.addOrUpdateConversationInfo(shortcut);
         assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
-            TEST_SHORTCUT_ID)).isNotNull();
+                TEST_SHORTCUT_ID)).isNotNull();
         assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
                 TEST_SHORTCUT_ID + "1")).isNull();
 
         NotificationListenerService listenerService =
                 mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
         listenerService.onNotificationPosted(mStatusBarNotification);
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs);
 
         ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
                 TEST_SHORTCUT_ID);
@@ -568,6 +573,8 @@
                 result.getParentNotificationChannel().getId());
         assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
         assertTrue(result.hasActiveNotifications());
+        assertFalse(result.hasBirthdayToday());
+        assertThat(result.getStatuses()).containsExactly(cs);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 28d313b..e71c2f5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -294,6 +294,8 @@
 
     private InputDevice createInputDevice(int id, boolean hasVibrator) {
         return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, hasVibrator, false, false, false /* hasSensor */);
+                null, hasVibrator, false, false, false /* hasSensor */, false /* hasBattery */);
+
+
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 5c67db7..91fd7a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -50,6 +50,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
@@ -408,6 +409,32 @@
     }
 
     @Test
+    public void testRestrictAppBoundsToOverrideBounds() {
+        final RootDisplayArea root =
+                new DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot(mWm);
+        final DisplayArea<DisplayArea> da = new DisplayArea<>(mWm, ANY, "Test_DA");
+        root.addChild(da, POSITION_TOP);
+        final Rect displayBounds = new Rect(0, 0, 1800, 2800);
+        final Rect displayAppBounds = new Rect(0, 100, 1800, 2800);
+        final Rect daBounds = new Rect(0, 1400, 1800, 2800);
+        root.setBounds(displayBounds);
+
+        // DA inherit parent app bounds.
+        final Configuration displayConfig = new Configuration();
+        displayConfig.windowConfiguration.setAppBounds(displayAppBounds);
+        root.onRequestedOverrideConfigurationChanged(displayConfig);
+
+        assertEquals(displayAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+
+        // Restrict DA appBounds to override Bounds
+        da.setBounds(daBounds);
+
+        final Rect expectedDaAppBounds = new Rect(daBounds);
+        expectedDaAppBounds.intersect(displayAppBounds);
+        assertEquals(expectedDaAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+    }
+
+    @Test
     public void testGetOrientation() {
         final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test");
         final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 82ffa765..37fb0e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -123,6 +123,15 @@
         updateDisplayFrames();
     }
 
+    void addWindowWithRawInsetsState(WindowState win) {
+        addWindow(win);
+        // Without mPerformLayout in display content, the window cannot see any insets. Override the
+        // insets state with the global one.
+        final InsetsState insetsState =
+                win.getDisplayContent().getInsetsStateController().getRawInsetsState();
+        win.mAboveInsetsState = insetsState;
+    }
+
     public void setRotation(int rotation, boolean includingWindows) {
         mRotation = rotation;
         updateDisplayFrames();
@@ -272,7 +281,7 @@
     @Test
     public void layoutWindowLw_fitStatusBars() {
         mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -283,7 +292,7 @@
     @Test
     public void layoutWindowLw_fitNavigationBars() {
         mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -294,7 +303,7 @@
     @Test
     public void layoutWindowLw_fitAllSides() {
         mWindow.mAttrs.setFitInsetsSides(Side.all());
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -305,7 +314,7 @@
     @Test
     public void layoutWindowLw_fitTopOnly() {
         mWindow.mAttrs.setFitInsetsSides(Side.TOP);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -315,11 +324,12 @@
 
     @Test
     public void layoutWindowLw_fitInsetsIgnoringVisibility() {
-        final InsetsState state = mWindow.getInsetsState();
+        final InsetsState state =
+                mDisplayContent.getInsetsStateController().getRawInsetsState();
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
         mWindow.mAttrs.setFitInsetsIgnoringVisibility(true);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -329,11 +339,12 @@
 
     @Test
     public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
-        final InsetsState state = mWindow.getInsetsState();
+        final InsetsState state =
+                mDisplayContent.getInsetsStateController().getRawInsetsState();
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
         mWindow.mAttrs.setFitInsetsIgnoringVisibility(false);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -349,8 +360,7 @@
         state.getSource(InsetsState.ITYPE_IME).setFrame(
                 0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
         mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
-        mWindow.mBehindIme = true;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -364,7 +374,7 @@
 
         mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout());
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -379,7 +389,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -395,7 +405,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -411,7 +421,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -427,7 +437,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -442,7 +452,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -457,11 +467,12 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+        mDisplayContent.getInsetsStateController().getRawInsetsState()
+                .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         final InsetsState requestedState = new InsetsState();
         requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
         mWindow.updateRequestedVisibility(requestedState);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -476,12 +487,13 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+        mDisplayContent.getInsetsStateController().getRawInsetsState()
+                .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         final InsetsState requestedState = new InsetsState();
         requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
         mWindow.updateRequestedVisibility(requestedState);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -497,7 +509,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -513,7 +525,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -529,7 +541,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -545,7 +557,7 @@
         mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY;
         mWindow.mAttrs.width = DISPLAY_WIDTH;
         mWindow.mAttrs.height = DISPLAY_HEIGHT;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -562,7 +574,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -576,7 +588,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -592,7 +604,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -608,7 +620,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -624,7 +636,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
 
@@ -638,7 +650,7 @@
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
-        addWindow(mWindow);
+        addWindowWithRawInsetsState(mWindow);
 
         final int forwardedInsetBottom = 50;
         mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
@@ -776,9 +788,13 @@
     public void testFixedRotationInsetsSourceFrame() {
         doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
-        final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController()
+                .getRawInsetsState().peekSource(ITYPE_STATUS_BAR));
+        final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+                .getSource(ITYPE_STATUS_BAR).getFrame();
         mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
-        final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+                .getSource(ITYPE_STATUS_BAR).getFrame();
 
         assertEquals(DISPLAY_WIDTH, frame.width());
         assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 77537a9..499507e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -300,6 +300,7 @@
         displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
         mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+        mImeWindow.mAboveInsetsState = state;
         mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
                 state, displayInfo, null /* displayCutout */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e6f24da..f91c9d0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index e0fd379..bf3ed69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -45,6 +45,7 @@
 
 import android.app.StatusBarManager;
 import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 
@@ -272,7 +273,6 @@
         final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
         navBar.setHasSurface(true);
         navBar.getControllableInsetProvider().setServerVisible(true);
-
         final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
         doNothing().when(policy).startAnimation(anyBoolean(), any());
 
@@ -337,11 +337,14 @@
     @UseTestDisplay(addWindows = W_ACTIVITY)
     @Test
     public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
-        addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
-                .getControllableInsetProvider().getSource().setVisible(false);
-        addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
-                .getControllableInsetProvider().getSource().setVisible(false);
-
+        final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
+                .getControllableInsetProvider().getSource();
+        final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
+                .getControllableInsetProvider().getSource();
+        statusBarSource.setVisible(false);
+        navBarSource.setVisible(false);
+        mAppWindow.mAboveInsetsState.addSource(navBarSource);
+        mAppWindow.mAboveInsetsState.addSource(statusBarSource);
         final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
         doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2766438..2107ab1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -59,25 +59,6 @@
 public class InsetsStateControllerTest extends WindowTestsBase {
 
     @Test
-    public void testStripForDispatch_notOwn() {
-        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
-        statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
-        assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
-    }
-
-    @Test
-    public void testStripForDispatch_own() {
-        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
-                .setWindow(statusBar, null, null);
-        statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
-        final InsetsState state = getController().getInsetsForWindow(statusBar);
-        assertNull(state.peekSource(ITYPE_STATUS_BAR));
-    }
-
-    @Test
     public void testStripForDispatch_navBar() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
@@ -142,14 +123,15 @@
         getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
 
         final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
-        app1.mBehindIme = true;
-
         final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
-        app2.mBehindIme = false;
+
+        app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
 
         getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME).isVisible());
-        assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME).isVisible());
+        assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME)
+                .isVisible());
+        assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME)
+                .isVisible());
     }
 
     @UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -158,7 +140,8 @@
         getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        app.mBehindIme = true;
+        app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+        app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
 
         getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
         assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
@@ -170,10 +153,10 @@
         getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        app.mBehindIme = false;
 
         getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+        assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+                .isVisible());
     }
 
     @UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -210,7 +193,8 @@
 
         // app won't get visible IME insets while above IME even when IME is visible.
         assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
-        assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+        assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+                .isVisible());
 
         // Reset invocation counter.
         clearInvocations(app);
@@ -219,6 +203,8 @@
         app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
         mDisplayContent.computeImeTarget(true);
         mDisplayContent.applySurfaceChangesTransaction();
+        app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+        app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
 
         // Make sure app got notified.
         verify(app, atLeast(1)).notifyInsetsChanged();
@@ -234,6 +220,8 @@
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+        app.mAboveInsetsState.set(getController().getRawInsetsState());
+        child.mAboveInsetsState.set(getController().getRawInsetsState());
         child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
 
         mDisplayContent.computeImeTarget(true);
@@ -242,7 +230,8 @@
 
         getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
         assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
-        assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+        assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+                .isVisible());
     }
 
     @UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -252,6 +241,7 @@
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+        app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
         child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
         child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
@@ -261,7 +251,8 @@
 
         getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
         assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
-        assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+        assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+                .isVisible());
     }
 
     @Test
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 6dfbbc7..bbd89b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -26,6 +26,7 @@
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -645,12 +646,11 @@
         assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
                 mActivity.getLetterboxInsets());
 
-        final BarController statusBarController =
-                mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
+        final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
         // The activity doesn't fill the display, so the letterbox of the rotated activity is
         // overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
         // be transparent.
-        assertFalse(statusBarController.isFullyTransparentAllowed(w));
+        assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
 
         // Make the activity fill the display.
         prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -660,7 +660,7 @@
 
         // The letterbox should only cover the notch area, so status bar can be transparent.
         assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
-        assertTrue(statusBarController.isFullyTransparentAllowed(w));
+        assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
     }
 
     @Test
@@ -1020,9 +1020,9 @@
         displayPolicy.onConfigurationChanged();
 
         final TestWindowToken token = createTestWindowToken(
-                WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
+                TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
-                new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
+                new WindowManager.LayoutParams(TYPE_STATUS_BAR);
         attrs.gravity = android.view.Gravity.TOP;
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 1607f01..a1f89ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -275,7 +275,7 @@
         imeSource.setFrame(imeFrame);
         imeSource.setVisible(true);
         w.updateRequestedVisibility(state);
-        w.mBehindIme = true;
+        w.mAboveInsetsState.addSource(imeSource);
 
         // With no insets or system decor all the frames incoming from PhoneWindowManager
         // are identical.
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3815326..0cb1255 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -79,7 +79,6 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -111,6 +110,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * A service that collects, aggregates, and persists application usage data.
@@ -162,7 +162,7 @@
     ShortcutServiceInternal mShortcutServiceInternal;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
-    private final SparseBooleanArray mUserUnlockedStates = new SparseBooleanArray();
+    private final CopyOnWriteArraySet<Integer> mUserUnlockedStates = new CopyOnWriteArraySet<>();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
     int mUsageSource;
 
@@ -333,7 +333,7 @@
 
         synchronized (mLock) {
             // User was started but never unlocked so no need to report a user stopped event
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 persistPendingEventsLocked(userId);
                 return;
             }
@@ -346,7 +346,7 @@
             if (userService != null) {
                 userService.userStopped();
             }
-            mUserUnlockedStates.put(userId, false);
+            mUserUnlockedStates.remove(userId);
             mUserState.put(userId, null); // release the service (mainly for GC)
         }
     }
@@ -360,6 +360,11 @@
             UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
         }
         synchronized (mLock) {
+            // This should be safe to add this early. Other than reportEventOrAddToQueue, every
+            // other user grabs the lock before accessing
+            // mUserUnlockedStates. reportEventOrAddToQueue does not depend on anything other than
+            // mUserUnlockedStates, and the lock will protect the handler.
+            mUserUnlockedStates.add(userId);
             // Create a user unlocked event to report
             final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
             unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
@@ -377,7 +382,6 @@
 
             initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(),
                     installedPackages);
-            mUserUnlockedStates.put(userId, true);
             final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId);
             if (userService == null) {
                 Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
@@ -780,12 +784,11 @@
     }
 
     private void reportEventOrAddToQueue(int userId, Event event) {
+        if (mUserUnlockedStates.contains(userId)) {
+            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            return;
+        }
         synchronized (mLock) {
-            if (mUserUnlockedStates.get(userId)) {
-                mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
-                return;
-            }
-
             LinkedList<Event> events = mReportedEvents.get(userId);
             if (events == null) {
                 events = new LinkedList<>();
@@ -823,7 +826,7 @@
 
         synchronized (mLock) {
             // This should never be called directly when the user is locked
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.wtf(TAG, "Failed to report event for locked user " + userId
                         + " (" + event.mPackage + "/" + event.mClass
                         + " eventType:" + event.mEventType
@@ -1006,7 +1009,7 @@
         final int tokenRemoved;
         synchronized (mLock) {
             final long timeRemoved = System.currentTimeMillis();
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 // If user is not unlocked and a package is removed for them, we will handle it
                 // when the user service is initialized and package manager is queried.
                 return;
@@ -1030,7 +1033,7 @@
      */
     private boolean pruneUninstalledPackagesData(int userId) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 return false; // user is no longer unlocked
             }
 
@@ -1050,7 +1053,7 @@
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
+            if (!mUserUnlockedStates.contains(UserHandle.USER_SYSTEM)) {
                 return false; // user is no longer unlocked
             }
 
@@ -1069,7 +1072,7 @@
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
             boolean obfuscateInstantApps) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query usage stats for locked user " + userId);
                 return null;
             }
@@ -1103,7 +1106,7 @@
     List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query configuration stats for locked user " + userId);
                 return null;
             }
@@ -1122,7 +1125,7 @@
     List<EventStats> queryEventStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query event stats for locked user " + userId);
                 return null;
             }
@@ -1140,7 +1143,7 @@
      */
     UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query events for locked user " + userId);
                 return null;
             }
@@ -1159,7 +1162,7 @@
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
             String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query package events for locked user " + userId);
                 return null;
             }
@@ -1203,7 +1206,7 @@
         final int userCount = mUserState.size();
         for (int i = 0; i < userCount; i++) {
             final int userId = mUserState.keyAt(i);
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 persistPendingEventsLocked(userId);
                 continue;
             }
@@ -1261,7 +1264,7 @@
                             final int numUsers = mUserState.size();
                             for (int user = 0; user < numUsers; user++) {
                                 final int userId = mUserState.keyAt(user);
-                                if (!mUserUnlockedStates.get(userId)) {
+                                if (!mUserUnlockedStates.contains(userId)) {
                                     continue;
                                 }
                                 ipw.println("user=" + userId);
@@ -1288,7 +1291,7 @@
                             final int numUsers = mUserState.size();
                             for (int user = 0; user < numUsers; user++) {
                                 final int userId = mUserState.keyAt(user);
-                                if (!mUserUnlockedStates.get(userId)) {
+                                if (!mUserUnlockedStates.contains(userId)) {
                                     continue;
                                 }
                                 ipw.println("user=" + userId);
@@ -1344,7 +1347,7 @@
                 idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
-                if (mUserUnlockedStates.get(userId)) {
+                if (mUserUnlockedStates.contains(userId)) {
                     if (checkin) {
                         mUserState.valueAt(i).checkin(idpw);
                     } else {
@@ -1382,7 +1385,7 @@
             ipw.println("the specified user does not exist.");
             return UserHandle.USER_NULL;
         }
-        if (!mUserUnlockedStates.get(userId)) {
+        if (!mUserUnlockedStates.contains(userId)) {
             ipw.println("the specified user is currently in a locked state.");
             return UserHandle.USER_NULL;
         }
@@ -2250,12 +2253,11 @@
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
+            if (!mUserUnlockedStates.contains(user)) {
+                Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+                return null;
+            }
             synchronized (mLock) {
-                if (!mUserUnlockedStates.get(user)) {
-                    Slog.w(TAG, "Failed to get backup payload for locked user " + user);
-                    return null;
-                }
-
                 // Check to ensure that only user 0's data is b/r for now
                 // Note: if backup and restore is enabled for users other than the system user, the
                 // #onUserUnlocked logic, specifically when the update mappings job is scheduled via
@@ -2275,7 +2277,7 @@
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
             synchronized (mLock) {
-                if (!mUserUnlockedStates.get(user)) {
+                if (!mUserUnlockedStates.contains(user)) {
                     Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
                     return;
                 }
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1faae42..a9dae89 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -138,6 +138,8 @@
      * Indicates if the session is for a conference call or not. If not defined, should be
      * considered {@code false}.
      * Boolean extra properties - {@code true} / {@code false}.
+     *
+     * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
      * @hide
      */
     @SystemApi
@@ -174,6 +176,8 @@
      * Indicates if the session can be extended to a conference call. If not defined, should be
      * considered {@code false}.
      * Boolean extra properties - {@code true} / {@code false}.
+     *
+     * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
index eddbb10..8762b6a 100644
--- a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
@@ -280,6 +280,12 @@
             "sip_config_path_header_string";
 
     /**
+     * The SIP User-Agent header value used by the IMS stack during IMS registration.
+     */
+    public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING =
+            "sip_config_sip_user_agent_header_string";
+
+    /**
      * SIP User part string in contact header
      */
     public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING =
@@ -292,12 +298,20 @@
             "sip_config_p_access_network_info_header_string";
 
     /**
-     * SIP P-last-access-network-info header string
+     * The SIP P-last-access-network-info header value, populated for networks that require this
+     * information to be provided in outgoing SIP messages.
      */
     public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING =
             "sip_config_p_last_access_network_info_header_string";
 
     /**
+     * The Cellular-Network-Info header value (See 3GPP 24.229, section 7.2.15), populated for
+     * networks that require this information to be provided as part of outgoing SIP messages.
+     */
+    public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING =
+            "sip_config_cellular_network_info_header_string";
+
+    /**
      * SIP P-associated-uri header string
      */
     public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING =
@@ -320,9 +334,11 @@
             KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING,
             KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING,
             KEY_SIP_CONFIG_PATH_HEADER_STRING,
+            KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING,
             KEY_SIP_CONFIG_URI_USER_PART_STRING,
             KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING,
             KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING,
+            KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING,
             KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 22aa18f..d17415a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2360,6 +2360,16 @@
      */
     String getContactFromEab(String contact);
 
+    /*
+     * Check whether the device supports RCS User Capability Exchange or not.
+     */
+    boolean getDeviceUceEnabled();
+
+    /*
+     * Set the device supports RCS User Capability Exchange.
+     */
+     void setDeviceUceEnabled(boolean isEnabled);
+
     /**
      * Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the
      * specified thresholds.
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
deleted file mode 100644
index db251b9..0000000
--- a/tests/FlickerTests/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "postsubmit": [
-    // Run tests on real device
-    {
-      "name": "FlickerTests",
-      "keywords": ["primary-device"]
-    },
-    // Also run the tests in the cloud
-    {
-      "name": "FlickerTests"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ba12fbe..6b6d21b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,9 +21,12 @@
 import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
 import com.android.server.wm.flicker.dsl.WmAssertionBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
 
-const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
 const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
 const val WALLPAPER_TITLE = "Wallpaper"
 
@@ -33,7 +36,7 @@
     enabled: Boolean = bugId == 0
 ) {
     all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
-        this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME)
     }
 }
 
@@ -43,7 +46,7 @@
     enabled: Boolean = bugId == 0
 ) {
     all("navBarWindowIsAlwaysVisible", bugId, enabled) {
-        this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME)
     }
 }
 
@@ -113,6 +116,18 @@
     }
 }
 
+fun WmAssertionBuilder.appWindowBecomesInVisible(
+    appName: String,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("appWindowBecomesInVisible", bugId, enabled) {
+        this.showsAppWindow(appName)
+                .then()
+                .hidesAppWindow(appName)
+    }
+}
+
 @JvmOverloads
 fun LayersAssertionBuilder.noUncoveredRegions(
     beginRotation: Int,
@@ -151,15 +166,15 @@
 ) {
     if (rotatesScreen) {
         all("navBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+            this.showsLayer(NAV_BAR_LAYER_NAME)
                     .then()
-                    .hidesLayer(NAVIGATION_BAR_WINDOW_TITLE)
+                    .hidesLayer(NAV_BAR_LAYER_NAME)
                     .then()
-                    .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+                    .showsLayer(NAV_BAR_LAYER_NAME)
         }
     } else {
         all("navBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+            this.showsLayer(NAV_BAR_LAYER_NAME)
         }
     }
 }
@@ -172,15 +187,15 @@
 ) {
     if (rotatesScreen) {
         all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+            this.showsLayer(STATUS_BAR_LAYER_NAME)
                     .then()
-                    hidesLayer(STATUS_BAR_WINDOW_TITLE)
+                    hidesLayer(STATUS_BAR_LAYER_NAME)
                     .then()
-                    .showsLayer(STATUS_BAR_WINDOW_TITLE)
+                    .showsLayer(STATUS_BAR_LAYER_NAME)
         }
     } else {
         all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+            this.showsLayer(STATUS_BAR_LAYER_NAME)
         }
     }
 }
@@ -196,15 +211,15 @@
     val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
 
     start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
-        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
     }
     end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
-        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos)
     }
 
     if (startingPos == endingPos) {
         all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
-            this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+            this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
         }
     }
 }
@@ -220,10 +235,10 @@
     val endingPos = WindowUtils.getStatusBarPosition(endRotation)
 
     start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
-        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, startingPos)
     }
     end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
-        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, endingPos)
     }
 }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index b569eda..1a47449 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -41,6 +41,9 @@
         wmHelper.waitForRotation(rotation)
         wmHelper.waitForNavBarStatusBarVisible()
         wmHelper.waitForAppTransitionIdle()
+
+        // Ensure WindowManagerService wait until all animations have completed
+        instrumentation.getUiAutomation().syncInputTransactions()
     } catch (e: RemoteException) {
         throw RuntimeException(e)
     }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 104758d..5381009 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -98,6 +98,8 @@
     private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
     private Context mSpyContext;
+    // Keep track of all created watchdogs to apply device config changes
+    private List<PackageWatchdog> mAllocatedWatchdogs;
     @Mock
     private ConnectivityModuleConnector mConnectivityModuleConnector;
     @Mock
@@ -112,7 +114,8 @@
         MockitoAnnotations.initMocks(this);
         new File(InstrumentationRegistry.getContext().getFilesDir(),
                 "package-watchdog.xml").delete();
-        adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG);
+        adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
+                Manifest.permission.WRITE_DEVICE_CONFIG);
         mTestLooper = new TestLooper();
         mSpyContext = spy(InstrumentationRegistry.getContext());
         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
@@ -157,12 +160,23 @@
                     return storedValue == null ? defaultValue : Long.parseLong(storedValue);
                 }
         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+                Boolean.toString(true), false);
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false);
+
+        mAllocatedWatchdogs = new ArrayList<>();
     }
 
     @After
     public void tearDown() throws Exception {
         dropShellPermissions();
         mSession.finishMocking();
+        mAllocatedWatchdogs.clear();
     }
 
     @Test
@@ -611,10 +625,6 @@
      */
     @Test
     public void testExplicitHealthCheckStateChanges() throws Exception {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
-
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
@@ -807,9 +817,6 @@
     /** Test default values are used when device property is invalid. */
     @Test
     public void testInvalidConfig_watchdogTriggerFailureCount() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(-1), /*makeDefault*/false);
@@ -835,9 +842,6 @@
     /** Test default values are used when device property is invalid. */
     @Test
     public void testInvalidConfig_watchdogTriggerDurationMillis() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(2), /*makeDefault*/false);
@@ -850,7 +854,6 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -862,7 +865,6 @@
 
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -917,9 +919,6 @@
     /** Test we are notified when enough failures are triggered within any window. */
     @Test
     public void testFailureTriggerWindow() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(3), /*makeDefault*/false);
@@ -933,11 +932,9 @@
         // Raise 2 failures at t=0 and t=900 respectively
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(900);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
 
         // Raise 2 failures at t=1100
         moveTimeForwardAndDispatch(200);
@@ -1303,15 +1300,15 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
                 Boolean.toString(enabled), /*makeDefault*/false);
-        //give time for DeviceConfig to broadcast the property value change
-        try {
-            Thread.sleep(SHORT_DURATION);
-        } catch (InterruptedException e) {
-            fail("Thread.sleep unexpectedly failed!");
+        // Call updateConfigs() so device config changes take effect immediately
+        for (PackageWatchdog watchdog : mAllocatedWatchdogs) {
+            watchdog.updateConfigs();
         }
     }
 
     private void moveTimeForwardAndDispatch(long milliSeconds) {
+        // Exhaust all due runnables now which shouldn't be executed after time-leap
+        mTestLooper.dispatchAll();
         mTestClock.moveTimeForward(milliSeconds);
         mTestLooper.moveTimeForward(milliSeconds);
         mTestLooper.dispatchAll();
@@ -1354,6 +1351,7 @@
             verify(mConnectivityModuleConnector).registerHealthListener(
                     mConnectivityModuleCallbackCaptor.capture());
         }
+        mAllocatedWatchdogs.add(watchdog);
         return watchdog;
     }
 
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0db2b2a..7b2a07f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1225,4 +1225,44 @@
             InstallUtils.dropShellPermissionIdentity();
         }
     }
+
+    /**
+     * Tests an app can be rolled back to the previous signing key.
+     *
+     * <p>The rollback capability in the signing lineage allows an app to be updated to an APK
+     * signed with a previous signing key in the lineage; however this often defeats the purpose
+     * of key rotation as a compromised key could then be used to roll an app back to the previous
+     * key. To avoid requiring the rollback capability to support app rollbacks the PackageManager
+     * allows an app to be rolled back to the previous signing key if the rollback install reason
+     * is set.
+     */
+    @Test
+    public void testRollbackAfterKeyRotation() throws Exception {
+        try {
+            InstallUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                    Manifest.permission.MANAGE_ROLLBACKS);
+
+            // Uninstall TestApp.A
+            Uninstall.packages(TestApp.A);
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+
+            // Install v1 of the app with the original signing key (without rollbacks enabled).
+            Install.single(TestApp.AOriginal1).commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+            // Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled.
+            Install.single(TestApp.ARotated2).setEnableRollback().commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+            // Roll back the app.
+            RollbackInfo available = waitForAvailableRollback(TestApp.A);
+            RollbackUtils.rollback(available.getRollbackId());
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        } finally {
+            InstallUtils.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index ef973ac..861d221 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -182,6 +182,24 @@
 
     @SmallTest
     @Test
+    public void setFunctionsNcmAndRndis() {
+        final long rndisPlusNcm = UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM;
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_NCM));
+        assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                rndisPlusNcm));
+        assertEquals(rndisPlusNcm, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_NCM));
+        assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+    }
+
+    @SmallTest
+    @Test
     public void enableAdb() {
         sendBootCompleteMessages(mUsbHandler);
         Message msg = mUsbHandler.obtainMessage(MSG_ENABLE_ADB);
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index f6a2846..e630da0 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -53,6 +53,7 @@
     jarjar_rules: "jarjar-rules.txt",
     static_libs: [
         "androidx.test.rules",
+        "bouncycastle-repackaged-unbundled",
         "FrameworksNetCommonTests",
         "frameworks-base-testutils",
         "frameworks-net-integration-testutils",
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index 076e41d..1abd39a 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -30,7 +30,7 @@
 
 import com.android.internal.net.VpnProfile;
 import com.android.net.module.util.ProxyUtils;
-import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 68e5ce0..bf73134 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1283,32 +1283,10 @@
     }
 
     private void updateUidNetworkingBlocked() {
-        // Changes the return value of the mock NetworkPolicyManager's isUidNetworkingBlocked method
-        // based on the current UID rules and restrict background setting. Note that the test never
-        // pretends to be a foreground app, so always declare no connectivity if background
-        // networking is not allowed.
-        switch (mUidRules) {
-            case RULE_REJECT_ALL:
-                when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
-                        .thenReturn(true);
-                break;
-
-            case RULE_REJECT_METERED:
-                when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(true)))
-                        .thenReturn(true);
-                when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(false)))
-                        .thenReturn(mRestrictBackground);
-                break;
-
-            case RULE_ALLOW_METERED:
-            case RULE_NONE:
-                when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
-                        .thenReturn(mRestrictBackground);
-                break;
-
-            default:
-                fail("Unknown policy rule " + mUidRules);
-        }
+        doAnswer(i -> NetworkPolicyManagerInternal.isUidNetworkingBlocked(
+                i.getArgument(0) /* uid */, mUidRules, i.getArgument(1) /* metered */,
+                mRestrictBackground)
+        ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
     }
 
     private void setUidRulesChanged(int uidRules) throws RemoteException {
@@ -6917,7 +6895,7 @@
         cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
                 mCellNetworkAgent);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        assertEquals(null, mCm.getActiveNetwork());
+        assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
 
@@ -6930,17 +6908,21 @@
         setUidRulesChanged(RULE_NONE);
         cellNetworkCallback.assertNoCallback();
 
-        // Restrict the network based on BackgroundRestricted.
+        // Restrict background data. Networking is not blocked because the network is unmetered.
         setRestrictBackgroundChanged(true);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        assertEquals(null, mCm.getActiveNetwork());
+        assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-
         setRestrictBackgroundChanged(true);
         cellNetworkCallback.assertNoCallback();
-        setRestrictBackgroundChanged(false);
+
+        setUidRulesChanged(RULE_ALLOW_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+
+        setRestrictBackgroundChanged(false);
         cellNetworkCallback.assertNoCallback();
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index e26bf19..e7d334e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -22,7 +22,6 @@
 
 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.junit.Assert.fail;
@@ -30,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -39,16 +39,20 @@
 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.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.Transport;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -253,7 +257,14 @@
         verify(mConfigReadWriteHelper).readFromDisk();
     }
 
-    private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            Set<ParcelUuid> activeSubscriptionGroups) {
+        return triggerSubscriptionTrackerCbAndGetSnapshot(
+                activeSubscriptionGroups, Collections.emptyMap());
+    }
+
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
 
@@ -267,8 +278,14 @@
                         argThat(val -> activeSubscriptionGroups.contains(val)),
                         eq(TEST_PACKAGE_NAME));
 
+        doAnswer(invocation -> {
+            return subIdToGroupMap.get(invocation.getArgument(0));
+        }).when(snapshot).getGroupForSubId(anyInt());
+
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         cb.onNewSnapshot(snapshot);
+
+        return snapshot;
     }
 
     private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
@@ -287,7 +304,7 @@
 
     @Test
     public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
-        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
         verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
     }
 
@@ -296,7 +313,7 @@
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
 
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Verify teardown after delay
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -311,13 +328,13 @@
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Simulate new SIM loaded right during teardown delay.
         mTestLooper.moveTimeForward(
                 VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
         mTestLooper.dispatchAll();
-        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
 
         // Verify that even after the full timeout duration, the VCN instance is not torn down
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -331,7 +348,7 @@
         final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
         // vcnInstance.
@@ -496,14 +513,73 @@
         mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
     }
 
+    private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
+        mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
+    }
+
+    private void verifyMergedNetworkCapabilities(
+            NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+        assertTrue(mergedCapabilities.hasTransport(transportType));
+        assertFalse(
+                mergedCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+    }
+
     @Test
-    public void testGetUnderlyingNetworkPolicy() throws Exception {
+    public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception {
+        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .build();
+
         VcnUnderlyingNetworkPolicy policy =
-                mVcnMgmtSvc.getUnderlyingNetworkPolicy(
-                        new NetworkCapabilities(), new LinkProperties());
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
 
         assertFalse(policy.isTeardownRequested());
-        assertNotNull(policy.getMergedNetworkCapabilities());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyTransportWifi() throws Exception {
+        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+        when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .setTransportInfo(wifiInfo)
+                        .build();
+
+        VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .build();
+
+        VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+        assertFalse(policy.isTeardownRequested());
+        assertEquals(nc, policy.getMergedNetworkCapabilities());
     }
 
     @Test(expected = SecurityException.class)
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
new file mode 100644
index 0000000..48e068d
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class UnderlyingNetworkTrackerTest {
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+    private static final int INITIAL_SUB_ID_1 = 1;
+    private static final int INITIAL_SUB_ID_2 = 2;
+
+    private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                    .build();
+    private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                    .build();
+    private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .build();
+
+    private static final LinkProperties INITIAL_LINK_PROPERTIES =
+            getLinkPropertiesWithName("initial_iface");
+    private static final LinkProperties UPDATED_LINK_PROPERTIES =
+            getLinkPropertiesWithName("updated_iface");
+
+    @Mock private Context mContext;
+    @Mock private VcnNetworkProvider mVcnNetworkProvider;
+    @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb;
+    @Mock private Network mNetwork;
+
+    @Captor private ArgumentCaptor<RouteSelectionCallback> mRouteSelectionCallbackCaptor;
+
+    private TestLooper mTestLooper;
+    private VcnContext mVcnContext;
+    private UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+        mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider));
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+        setupSystemService(
+                mContext,
+                mConnectivityManager,
+                Context.CONNECTIVITY_SERVICE,
+                ConnectivityManager.class);
+        setupSystemService(
+                mContext,
+                mSubscriptionManager,
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                SubscriptionManager.class);
+
+        List<SubscriptionInfo> initialSubInfos =
+                Arrays.asList(
+                        getSubscriptionInfoForSubId(INITIAL_SUB_ID_1),
+                        getSubscriptionInfoForSubId(INITIAL_SUB_ID_2));
+        when(mSubscriptionManager.getSubscriptionsInGroup(eq(SUB_GROUP)))
+                .thenReturn(initialSubInfos);
+
+        mUnderlyingNetworkTracker =
+                new UnderlyingNetworkTracker(
+                        mVcnContext,
+                        SUB_GROUP,
+                        Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET),
+                        mNetworkTrackerCb);
+    }
+
+    private static LinkProperties getLinkPropertiesWithName(String iface) {
+        LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(iface);
+        return linkProperties;
+    }
+
+    private SubscriptionInfo getSubscriptionInfoForSubId(int subId) {
+        SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
+        when(subInfo.getSubscriptionId()).thenReturn(subId);
+        return subInfo;
+    }
+
+    @Test
+    public void testNetworkCallbacksRegisteredOnStartup() {
+        // verify NetworkCallbacks registered when instantiated
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getWifiRequest()),
+                        any(),
+                        any(NetworkBringupCallback.class));
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getCellRequestForSubId(INITIAL_SUB_ID_1)),
+                        any(),
+                        any(NetworkBringupCallback.class));
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getCellRequestForSubId(INITIAL_SUB_ID_2)),
+                        any(),
+                        any(NetworkBringupCallback.class));
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getRouteSelectionRequest()),
+                        any(),
+                        any(RouteSelectionCallback.class));
+
+        verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP));
+    }
+
+    private NetworkRequest getWifiRequest() {
+        return getExpectedRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+    }
+
+    private NetworkRequest getCellRequestForSubId(int subId) {
+        return getExpectedRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+                .build();
+    }
+
+    private NetworkRequest getRouteSelectionRequest() {
+        return getExpectedRequestBase().build();
+    }
+
+    private NetworkRequest.Builder getExpectedRequestBase() {
+        return new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+    }
+
+    @Test
+    public void testTeardown() {
+        mUnderlyingNetworkTracker.teardown();
+
+        // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x
+        // for each subId)
+        verify(mConnectivityManager, times(3))
+                .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+        verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class));
+    }
+
+    @Test
+    public void testUnderlyingNetworkRecordEquals() {
+        UnderlyingNetworkRecord recordA =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        UnderlyingNetworkRecord recordB =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        UnderlyingNetworkRecord recordC =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        UPDATED_NETWORK_CAPABILITIES,
+                        UPDATED_LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        assertEquals(recordA, recordB);
+        assertNotEquals(recordA, recordC);
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkChange() {
+        verifyRegistrationOnAvailableAndGetCallback();
+    }
+
+    private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() {
+        return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
+    }
+
+    private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback(
+            NetworkCapabilities networkCapabilities) {
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getRouteSelectionRequest()),
+                        any(),
+                        mRouteSelectionCallbackCaptor.capture());
+
+        RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
+        cb.onAvailable(mNetwork);
+        cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+        cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
+        cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        networkCapabilities,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        return cb;
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        UPDATED_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        UPDATED_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onNetworkSuspended(mNetwork);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        SUSPENDED_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkResumed() {
+        RouteSelectionCallback cb =
+                verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
+
+        cb.onNetworkResumed(mNetwork);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForBlocked() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        true /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkLoss() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onLost(mNetwork);
+
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+    }
+
+    @Test
+    public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+
+        // Verify no more calls to the UnderlyingNetworkTrackerCallback when the
+        // UnderlyingNetworkRecord does not actually change
+        verifyNoMoreInteractions(mNetworkTrackerCb);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index b4d39bf..4d92fb9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -94,7 +94,7 @@
 
         doReturn(mUnderlyingNetworkTracker)
                 .when(mDeps)
-                .newUnderlyingNetworkTracker(any(), any(), any());
+                .newUnderlyingNetworkTracker(any(), any(), any(), any());
     }
 
     @Before
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index ef26532..c64f4bc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.AlarmManager;
@@ -948,8 +949,9 @@
      * has been set up).
      */
     public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
-            @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs,
-            @Nullable Bundle extraScanningParams) {
+            @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+            @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+            @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
         if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);