Merge "Update Framework from Jetpack." into sc-dev
diff --git a/Android.bp b/Android.bp
index 7e68986..240d803 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,7 @@
         ":framework_native_aidl",
         ":gatekeeper_aidl",
         ":gsiservice_aidl",
+        ":idmap2_aidl",
         ":idmap2_core_aidl",
         ":incidentcompanion_aidl",
         ":inputconstants_aidl",
@@ -402,6 +403,7 @@
         ":framework-mediaprovider-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
+        ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
@@ -422,6 +424,7 @@
         "framework-mediaprovider.stubs.module_lib",
         "framework-permission.stubs.module_lib",
         "framework-permission-s.stubs.module_lib",
+        "framework-scheduling.stubs.module_lib",
         "framework-sdkextensions.stubs.module_lib",
         "framework-statsd.stubs.module_lib",
         "framework-tethering.stubs.module_lib",
@@ -442,6 +445,7 @@
         "framework-mediaprovider.impl",
         "framework-permission.impl",
         "framework-permission-s.impl",
+        "framework-scheduling.impl",
         "framework-sdkextensions.impl",
         "framework-statsd.impl",
         "framework-tethering.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 3f2e898..4bd524f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -315,6 +315,7 @@
         "framework-mediaprovider.stubs",
         "framework-permission.stubs",
         "framework-permission-s.stubs",
+        "framework-scheduling.stubs",
         "framework-sdkextensions.stubs",
         "framework-statsd.stubs",
         "framework-tethering.stubs",
@@ -338,6 +339,7 @@
         "framework-mediaprovider.stubs.system",
         "framework-permission.stubs.system",
         "framework-permission-s.stubs.system",
+        "framework-scheduling.stubs.system",
         "framework-sdkextensions.stubs.system",
         "framework-statsd.stubs.system",
         "framework-tethering.stubs.system",
@@ -377,6 +379,7 @@
         "framework-mediaprovider.stubs.system",
         "framework-permission.stubs.system",
         "framework-permission-s.stubs.system",
+        "framework-scheduling.stubs.system",
         "framework-sdkextensions.stubs.system",
         "framework-statsd.stubs.system",
         "framework-tethering.stubs.system",
diff --git a/apex/media/Android.bp b/apex/media/Android.bp
index 5f1bd37..d6666f5 100644
--- a/apex/media/Android.bp
+++ b/apex/media/Android.bp
@@ -18,3 +18,10 @@
         "//frameworks/av/apex/testing",
     ],
 }
+
+sdk {
+    name: "media-module-sdk",
+    java_sdk_libs: [
+        "framework-media",
+    ],
+}
diff --git a/api/Android.bp b/api/Android.bp
index d5c6bf6..ac2f083 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -37,6 +37,7 @@
         ":framework-mediaprovider{.public.api.txt}",
         ":framework-permission{.public.api.txt}",
         ":framework-permission-s{.public.api.txt}",
+        ":framework-scheduling{.public.api.txt}",
         ":framework-sdkextensions{.public.api.txt}",
         ":framework-statsd{.public.api.txt}",
         ":framework-tethering{.public.api.txt}",
@@ -75,6 +76,7 @@
         ":framework-mediaprovider{.public.stubs.source}",
         ":framework-permission{.public.stubs.source}",
         ":framework-permission-s{.public.stubs.source}",
+        ":framework-scheduling{.public.stubs.source}",
         ":framework-sdkextensions{.public.stubs.source}",
         ":framework-statsd{.public.stubs.source}",
         ":framework-tethering{.public.stubs.source}",
@@ -99,6 +101,7 @@
         ":framework-mediaprovider{.public.removed-api.txt}",
         ":framework-permission{.public.removed-api.txt}",
         ":framework-permission-s{.public.removed-api.txt}",
+        ":framework-scheduling{.public.removed-api.txt}",
         ":framework-sdkextensions{.public.removed-api.txt}",
         ":framework-statsd{.public.removed-api.txt}",
         ":framework-tethering{.public.removed-api.txt}",
@@ -133,6 +136,7 @@
         ":framework-mediaprovider{.system.api.txt}",
         ":framework-permission{.system.api.txt}",
         ":framework-permission-s{.system.api.txt}",
+        ":framework-scheduling{.system.api.txt}",
         ":framework-sdkextensions{.system.api.txt}",
         ":framework-statsd{.system.api.txt}",
         ":framework-tethering{.system.api.txt}",
@@ -167,6 +171,7 @@
         ":framework-mediaprovider{.system.removed-api.txt}",
         ":framework-permission{.system.removed-api.txt}",
         ":framework-permission-s{.system.removed-api.txt}",
+        ":framework-scheduling{.system.removed-api.txt}",
         ":framework-sdkextensions{.system.removed-api.txt}",
         ":framework-statsd{.system.removed-api.txt}",
         ":framework-tethering{.system.removed-api.txt}",
@@ -201,6 +206,7 @@
         ":framework-mediaprovider{.module-lib.api.txt}",
         ":framework-permission{.module-lib.api.txt}",
         ":framework-permission-s{.module-lib.api.txt}",
+        ":framework-scheduling{.module-lib.api.txt}",
         ":framework-sdkextensions{.module-lib.api.txt}",
         ":framework-statsd{.module-lib.api.txt}",
         ":framework-tethering{.module-lib.api.txt}",
@@ -234,6 +240,7 @@
         ":framework-mediaprovider{.module-lib.removed-api.txt}",
         ":framework-permission{.module-lib.removed-api.txt}",
         ":framework-permission-s{.module-lib.removed-api.txt}",
+        ":framework-scheduling{.module-lib.removed-api.txt}",
         ":framework-sdkextensions{.module-lib.removed-api.txt}",
         ":framework-statsd{.module-lib.removed-api.txt}",
         ":framework-tethering{.module-lib.removed-api.txt}",
diff --git a/core/api/current.txt b/core/api/current.txt
index 1296dd19..6c1c79b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1295,6 +1295,7 @@
     field public static final int spinnerMode = 16843505; // 0x10102f1
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
+    field public static final int splashScreenTheme = 16844336; // 0x1010630
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
     field public static final int splitName = 16844105; // 0x1010549
     field public static final int splitTrack = 16843852; // 0x101044c
@@ -1614,6 +1615,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundBlurRadius = 16844331; // 0x101062b
     field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
     field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
@@ -1654,6 +1656,10 @@
     field public static final int windowShowAnimation = 16842934; // 0x10100b6
     field public static final int windowShowWallpaper = 16843410; // 0x1010292
     field public static final int windowSoftInputMode = 16843307; // 0x101022b
+    field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
+    field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
+    field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
+    field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
     field public static final int windowSplashscreenContent = 16844132; // 0x1010564
     field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3
     field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
@@ -1740,6 +1746,9 @@
     field public static final int dialog_min_width_minor = 17104900; // 0x1050004
     field public static final int notification_large_icon_height = 17104902; // 0x1050006
     field public static final int notification_large_icon_width = 17104901; // 0x1050005
+    field public static final int system_app_widget_background_radius = 17104904; // 0x1050008
+    field public static final int system_app_widget_inner_radius = 17104905; // 0x1050009
+    field public static final int system_app_widget_internal_padding = 17104906; // 0x105000a
     field public static final int thumbnail_height = 17104897; // 0x1050001
     field public static final int thumbnail_width = 17104898; // 0x1050002
   }
@@ -3864,6 +3873,7 @@
     method @Nullable public android.net.Uri getReferrer();
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
+    method @NonNull public final android.window.SplashScreen getSplashScreen();
     method public int getTaskId();
     method public final CharSequence getTitle();
     method public final int getTitleColor();
@@ -6652,6 +6662,7 @@
     method @NonNull public java.time.LocalTime getCustomNightModeEnd();
     method @NonNull public java.time.LocalTime getCustomNightModeStart();
     method public int getNightMode();
+    method public void setApplicationNightMode(int);
     method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
     method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
     method public void setNightMode(int);
@@ -9819,6 +9830,7 @@
     method public int getMimeTypeCount();
     method public long getTimestamp();
     method public boolean hasMimeType(String);
+    method public boolean isStyledText();
     method public void setExtras(android.os.PersistableBundle);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
@@ -10225,12 +10237,15 @@
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
+    method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
+    method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
     method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
     method public abstract int checkSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
     method @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)") public abstract int checkUriPermission(@Nullable android.net.Uri, @Nullable String, @Nullable String, int, int, int);
+    method @NonNull public int[] checkUriPermissions(@NonNull java.util.List<android.net.Uri>, int, int, int);
     method @Deprecated public abstract void clearWallpaper() throws java.io.IOException;
     method @NonNull public android.content.Context createAttributionContext(@Nullable String);
     method public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration);
@@ -12777,6 +12792,7 @@
     method @NonNull public android.content.pm.ShortcutInfo.Builder setPersons(@NonNull android.app.Person[]);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setRank(int);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setShortLabel(@NonNull CharSequence);
+    method @NonNull public android.content.pm.ShortcutInfo.Builder setStartingTheme(int);
   }
 
   public class ShortcutManager {
@@ -21896,6 +21912,7 @@
     field public static final String KEY_COLOR_RANGE = "color-range";
     field public static final String KEY_COLOR_STANDARD = "color-standard";
     field public static final String KEY_COLOR_TRANSFER = "color-transfer";
+    field public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
     field public static final String KEY_COMPLEXITY = "complexity";
     field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
     field public static final String KEY_DURATION = "durationUs";
@@ -38076,6 +38093,10 @@
     method public final void setNotificationsShown(String[]);
     method public final void snoozeNotification(String, long);
     method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+    field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
+    field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
+    field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
+    field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38084,6 +38105,7 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types";
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
@@ -49359,6 +49381,7 @@
     method public void setAllowEnterTransitionOverlap(boolean);
     method public void setAllowReturnTransitionOverlap(boolean);
     method public void setAttributes(android.view.WindowManager.LayoutParams);
+    method public void setBackgroundBlurRadius(int);
     method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setBackgroundDrawableResource(@DrawableRes int);
     method public void setCallback(android.view.Window.Callback);
@@ -55815,6 +55838,25 @@
 
 }
 
+package android.window {
+
+  public interface SplashScreen {
+    method public void setOnExitAnimationListener(@Nullable android.window.SplashScreen.OnExitAnimationListener);
+  }
+
+  public static interface SplashScreen.OnExitAnimationListener {
+    method public void onSplashScreenExit(@NonNull android.window.SplashScreenView);
+  }
+
+  public final class SplashScreenView extends android.widget.FrameLayout {
+    method public long getIconAnimationDurationMillis();
+    method public long getIconAnimationStartMillis();
+    method @Nullable public android.view.View getIconView();
+    method public void remove();
+  }
+
+}
+
 package javax.microedition.khronos.egl {
 
   public interface EGL {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4e25625..51d5139 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -223,6 +223,14 @@
     field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
   }
 
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor public VpnTransportInfo(int);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+    field public final int type;
+  }
+
 }
 
 package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d7dc6ef..3d7a09c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -82,6 +82,7 @@
     field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
     field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
+    field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
     field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
     field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
     field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
@@ -245,6 +246,7 @@
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+    field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
     field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
     field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
@@ -678,6 +680,7 @@
   }
 
   public static class Notification.Action implements android.os.Parcelable {
+    field public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; // 0xc
     field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
   }
 
@@ -870,7 +873,6 @@
   }
 
   public class DevicePolicyManager {
-    method public boolean canAdminGrantSensorsPermissionsForUser(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -2813,16 +2815,48 @@
 
 package android.graphics.fonts {
 
+  public final class FontFamilyUpdateRequest {
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.FontFamily> getFontFamilies();
+    method @NonNull public java.util.List<android.graphics.fonts.FontFileUpdateRequest> getFontFileUpdateRequests();
+  }
+
+  public static final class FontFamilyUpdateRequest.Builder {
+    ctor public FontFamilyUpdateRequest.Builder();
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.FontFamily);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFileUpdateRequest(@NonNull android.graphics.fonts.FontFileUpdateRequest);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest build();
+  }
+
+  public static final class FontFamilyUpdateRequest.Font {
+    ctor public FontFamilyUpdateRequest.Font(@NonNull String, @NonNull android.graphics.fonts.FontStyle, @NonNull java.util.List<android.graphics.fonts.FontVariationAxis>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontVariationAxis> getAxes();
+    method @NonNull public String getPostScriptName();
+    method @NonNull public android.graphics.fonts.FontStyle getStyle();
+  }
+
+  public static final class FontFamilyUpdateRequest.FontFamily {
+    ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts();
+    method @NonNull public String getName();
+  }
+
+  public final class FontFileUpdateRequest {
+    ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]);
+    method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    method @NonNull public byte[] getSignature();
+  }
+
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -7085,6 +7119,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -7106,6 +7141,10 @@
     field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
   }
 
+  public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
+    method public void onComplete();
+  }
+
   @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
     ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
     method @Deprecated public void onTetheringFailed();
@@ -7190,6 +7229,7 @@
     method public void close();
     method @NonNull public String getInterfaceName();
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
   }
 
   public static class IpSecTransform.Builder {
@@ -7333,6 +7373,7 @@
     method @NonNull public int[] getAdministratorUids();
     method @Nullable public String getSsid();
     method @NonNull public int[] getTransportTypes();
+    method public boolean isPrivateDnsBroken();
     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
@@ -7347,6 +7388,7 @@
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
     method @NonNull public android.net.NetworkCapabilities build();
+    method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
     method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -7462,6 +7504,26 @@
     ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
   }
 
+  public final class OemNetworkPreferences implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR;
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4
+    field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0
+  }
+
+  public static final class OemNetworkPreferences.Builder {
+    ctor public OemNetworkPreferences.Builder();
+    ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences);
+    method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int);
+    method @NonNull public android.net.OemNetworkPreferences build();
+    method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String);
+  }
+
   public abstract class QosCallback {
     ctor public QosCallback();
     method public void onError(@NonNull android.net.QosCallbackException);
@@ -8570,6 +8632,7 @@
   public class SystemConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String);
   }
 
   public class SystemProperties {
@@ -10358,6 +10421,7 @@
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
     method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException;
+    method public long onGetAnrDelayMillis(@NonNull String, int);
     method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException;
     method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException;
     field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
@@ -11049,6 +11113,8 @@
   }
 
   public static final class CarrierConfigManager.Wifi {
+    field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool";
+    field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool";
     field public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT = "wifi.hotspot_maximum_client_count";
     field public static final String KEY_PREFIX = "wifi.";
     field public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED = "wifi.suggestion_ssid_list_with_mac_randomization_disabled";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 694507d..2a9b318 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -837,14 +837,15 @@
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -1348,6 +1349,12 @@
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
+  public class NetworkPolicyManager {
+    method public boolean getRestrictBackground();
+    method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration);
+    method public void setRestrictBackground(boolean);
+  }
+
   public class NetworkStack {
     method public static void setServiceForTest(@Nullable android.os.IBinder);
   }
@@ -2723,6 +2730,10 @@
     field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
   }
 
+  public final class SplashScreenView extends android.widget.FrameLayout {
+    method @Nullable public android.view.View getBrandingView();
+  }
+
   public final class StartingWindowInfo implements android.os.Parcelable {
     ctor public StartingWindowInfo();
     method public int describeContents();
@@ -2742,6 +2753,7 @@
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
     method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
+    method @BinderThread public void copySplashScreenView(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 3c67e44..87fb5b1 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -487,6 +487,8 @@
     
 GetterSetterNames: android.location.LocationRequest#isLowPowerMode():
     
+GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground():
+    Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground`
 GetterSetterNames: android.os.IncidentReportArgs#isAll():
     
 GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied():
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4728f11..992d054 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -139,6 +139,8 @@
 import android.widget.AdapterView;
 import android.widget.Toast;
 import android.widget.Toolbar;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -961,6 +963,10 @@
 
     private UiTranslationController mUiTranslationController;
 
+    private SplashScreen mSplashScreen;
+    /** @hide */
+    SplashScreenView mSplashScreenView;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -1603,6 +1609,23 @@
     }
 
     /**
+     * Get the interface that activity use to talk to the splash screen.
+     * @see SplashScreen
+     */
+    public final @NonNull SplashScreen getSplashScreen() {
+        return getOrCreateSplashScreen();
+    }
+
+    private SplashScreen getOrCreateSplashScreen() {
+        synchronized (this) {
+            if (mSplashScreen == null) {
+                mSplashScreen = new SplashScreen.SplashScreenImpl(this);
+            }
+            return mSplashScreen;
+        }
+    }
+
+    /**
      * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
      * the attribute {@link android.R.attr#persistableMode} set to
      * <code>persistAcrossReboots</code>.
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 401f8cc..e3b5e9a 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -46,9 +46,9 @@
     }
 
     /** Reports {@link Activity#onResume()} is done. */
-    public void activityResumed(IBinder token) {
+    public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
         try {
-            getActivityClientController().activityResumed(token);
+            getActivityClientController().activityResumed(token, handleSplashScreenExit);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -488,6 +488,17 @@
         }
     }
 
+    /**
+     * Reports the splash screen view has attached to client.
+     */
+    void reportSplashScreenAttached(IBinder token) {
+        try {
+            getActivityClientController().splashScreenAttached(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     public static ActivityClient getInstance() {
         return sInstance.get();
     }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 28da1c3..73cc13c8 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -160,6 +160,12 @@
     public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
 
     /**
+     * Specific a theme for a splash screen window.
+     * @hide
+     */
+    public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
+
+    /**
      * Callback for when the last frame of the animation is played.
      * @hide
      */
@@ -398,6 +404,7 @@
     private IBinder mLaunchCookie;
     private IRemoteTransition mRemoteTransition;
     private boolean mOverrideTaskTransition;
+    private int mSplashScreenThemeResId;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -1147,6 +1154,7 @@
         mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
                 KEY_REMOTE_TRANSITION));
         mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
+        mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME);
     }
 
     /**
@@ -1333,6 +1341,14 @@
     }
 
     /**
+     * Gets whether the activity want to be launched as other theme for the splash screen.
+     * @hide
+     */
+    public int getSplashScreenThemeResId() {
+        return mSplashScreenThemeResId;
+    }
+
+    /**
      * Sets whether the activity is to be launched into LockTask mode.
      *
      * Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1838,6 +1854,9 @@
         if (mOverrideTaskTransition) {
             b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
         }
+        if (mSplashScreenThemeResId != 0) {
+            b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 70fa444..233f737 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -37,6 +38,7 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 
 import java.util.List;
 
@@ -243,6 +245,22 @@
     }
 
     /**
+     * Notify the server that splash screen of the given task has been copied"
+     *
+     * @param taskId Id of task to handle the material to reconstruct the splash screen view.
+     * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+     * @hide
+     */
+    public void onSplashScreenViewCopyFinished(int taskId,
+            @Nullable SplashScreenViewParcelable parcelable) {
+        try {
+            getService().onSplashScreenViewCopyFinished(taskId, parcelable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the default limit on the number of recents that an app can make.
      * @hide
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bb6a774..3d9f612 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -171,12 +171,15 @@
 import android.view.ViewDebug;
 import android.view.ViewManager;
 import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.autofill.AutofillId;
 import android.view.translation.TranslationSpec;
 import android.webkit.WebView;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -185,6 +188,7 @@
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -227,6 +231,7 @@
 import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
@@ -285,6 +290,7 @@
     /** Use background GC policy and default JIT threshold. */
     private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
 
+    private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000;
     /**
      * Denotes an invalid sequence number corresponding to a process state change.
      */
@@ -486,6 +492,8 @@
     final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
 
+    private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
+
     final GcIdler mGcIdler = new GcIdler();
     final PurgeIdler mPurgeIdler = new PurgeIdler();
 
@@ -1930,6 +1938,8 @@
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
 
+        public static final int REMOVE_SPLASH_SCREEN_VIEW = 172;
+
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -1976,6 +1986,8 @@
                     case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
                     case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                         return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+                    case REMOVE_SPLASH_SCREEN_VIEW:
+                        return "REMOVE_SPLASH_SCREEN_VIEW";
                 }
             }
             return Integer.toString(code);
@@ -2169,6 +2181,9 @@
                 case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                     handleFinishInstrumentationWithoutRestart();
                     break;
+                case REMOVE_SPLASH_SCREEN_VIEW:
+                    handleRemoveSplashScreenView((ActivityClientRecord) msg.obj);
+                    break;
             }
             Object obj = msg.obj;
             if (obj instanceof SomeArgs) {
@@ -3955,6 +3970,106 @@
     }
 
     /**
+     * Register a splash screen manager to this process.
+     */
+    public void registerSplashScreenManager(
+            @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
+        synchronized (this) {
+            mSplashScreenGlobal = manager;
+        }
+    }
+
+    @Override
+    public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
+        synchronized (this) {
+            return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
+        }
+    }
+
+    @Override
+    public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+            @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+        final DecorView decorView = (DecorView) r.window.peekDecorView();
+        if (parcelable != null && decorView != null) {
+            createSplashScreen(r, decorView, parcelable);
+        } else {
+            // shouldn't happen!
+            Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
+        }
+    }
+
+    private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
+            SplashScreenView.SplashScreenViewParcelable parcelable) {
+        final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
+        final SplashScreenView view = builder.createFromParcel(parcelable).build();
+        decorView.addView(view);
+        view.cacheRootWindow(r.window);
+        view.makeSystemUIColorsTransparent();
+        r.activity.mSplashScreenView = view;
+        view.requestLayout();
+        // Ensure splash screen view is shown before remove the splash screen window.
+        final ViewRootImpl impl = decorView.getViewRootImpl();
+        final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
+        final AtomicBoolean notified = new AtomicBoolean();
+        if (hardwareEnabled) {
+            final Runnable frameCommit = new Runnable() {
+                        @Override
+                        public void run() {
+                            view.post(() -> {
+                                if (!notified.get()) {
+                                    view.getViewTreeObserver().unregisterFrameCommitCallback(this);
+                                    ActivityClient.getInstance().reportSplashScreenAttached(
+                                            r.token);
+                                    notified.set(true);
+                                }
+                            });
+                        }
+                    };
+            view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
+        } else {
+            final ViewTreeObserver.OnDrawListener onDrawListener =
+                    new ViewTreeObserver.OnDrawListener() {
+                        @Override
+                        public void onDraw() {
+                            view.post(() -> {
+                                if (!notified.get()) {
+                                    view.getViewTreeObserver().removeOnDrawListener(this);
+                                    ActivityClient.getInstance().reportSplashScreenAttached(
+                                            r.token);
+                                    notified.set(true);
+                                }
+                            });
+                        }
+                    };
+            view.getViewTreeObserver().addOnDrawListener(onDrawListener);
+        }
+    }
+
+    @Override
+    public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
+        if (r.activity.mSplashScreenView != null) {
+            Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r);
+            mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT);
+            synchronized (this) {
+                if (mSplashScreenGlobal != null) {
+                    mSplashScreenGlobal.dispatchOnExitAnimation(r.token,
+                            r.activity.mSplashScreenView);
+                }
+            }
+        }
+    }
+
+    /**
+     * Force remove splash screen view.
+     */
+    private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) {
+        if (r.activity.mSplashScreenView != null) {
+            r.activity.mSplashScreenView.remove();
+            r.activity.mSplashScreenView = null;
+        }
+    }
+
+    /**
      * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
      * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
      * onPictureInPictureRequested to enter picture-in-picture.
@@ -5174,6 +5289,11 @@
         r.setState(ON_DESTROY);
         mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         schedulePurgeIdler();
+        synchronized (this) {
+            if (mSplashScreenGlobal != null) {
+                mSplashScreenGlobal.tokenDestroyed(r.token);
+            }
+        }
         // updatePendingActivityConfiguration() reads from mActivities to update
         // ActivityClientRecord which runs in a different thread. Protect modifications to
         // mActivities to avoid race.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 0e1c827..cf5fd14 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
 import android.os.IBinder;
 import android.util.MergedConfiguration;
 import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
@@ -158,6 +159,16 @@
     /** Request that an activity enter picture-in-picture. */
     public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
 
+    /** Whether the activity want to handle splash screen exit animation */
+    public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token);
+
+    /** Attach a splash screen window view to the top of the activity */
+    public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+            @NonNull SplashScreenViewParcelable parcelable);
+
+    /** Hand over the splash screen window view to the activity */
+    public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+
     /** Perform activity launch. */
     public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9a20e0f..85fb543 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.StrictMode.vmIncorrectContextUseEnabled;
 
@@ -105,6 +106,7 @@
 import java.nio.ByteOrder;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -2181,6 +2183,18 @@
         }
     }
 
+    @NonNull
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            int modeFlags) {
+        try {
+            return ActivityManager.getService().checkUriPermissions(uris, pid, uid, modeFlags,
+                    null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @Override
     public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -2207,12 +2221,30 @@
         return PackageManager.PERMISSION_DENIED;
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        int pid = Binder.getCallingPid();
+        if (pid != Process.myPid()) {
+            return checkUriPermissions(uris, pid, Binder.getCallingUid(), modeFlags);
+        }
+        int[] res = new int[uris.size()];
+        Arrays.fill(res, PERMISSION_DENIED);
+        return res;
+    }
+
     @Override
     public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
         return checkUriPermission(uri, Binder.getCallingPid(),
                 Binder.getCallingUid(), modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return checkUriPermissions(uris, Binder.getCallingPid(), Binder.getCallingUid(), modeFlags);
+    }
+
     @Override
     public int checkUriPermission(Uri uri, String readPermission,
             String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9d3286f..bb743b8 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -34,7 +34,7 @@
  */
 interface IActivityClientController {
     oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
-    oneway void activityResumed(in IBinder token);
+    oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
     oneway void activityTopResumedStateLost();
     /**
      * Notifies that the activity has completed paused. This call is not one-way because it can make
@@ -142,4 +142,7 @@
      * on the back stack.
      */
     oneway void onBackPressedOnTaskRoot(in IBinder token);
+
+    /** Reports that the splash screen view has attached to activity.  */
+    oneway void splashScreenAttached(in IBinder token);
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e73636f..4ad13e1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -220,6 +220,8 @@
     int getProcessLimit();
     int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
             in IBinder callerToken);
+    int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode,
+                in IBinder callerToken);
     void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
             int mode, int userId);
     void revokeUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 38a3e70..542f754 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -70,6 +70,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
 
@@ -348,4 +349,10 @@
      * Clears launch params for given packages.
      */
     void clearLaunchParamsForPackages(in List<String> packageNames);
+
+    /**
+     * A splash screen view has copied.
+     */
+    void onSplashScreenViewCopyFinished(int taskId,
+            in SplashScreenView.SplashScreenViewParcelable material);
 }
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 0ba5bec..f71eebdc 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -62,6 +62,16 @@
     int getNightMode();
 
     /**
+     * Sets the dark mode for the given application. This setting is persisted and will override the
+     * system configuration for this application.
+     *   1 - notnight mode
+     *   2 - night mode
+     *   3 - automatic mode switching
+     * @throws RemoteException
+     */
+    void setApplicationNightMode(in int mode);
+
+    /**
      * Tells if UI mode is locked or not.
      */
     boolean isUiModeLocked();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b6fc47f..77daf8d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1604,6 +1604,14 @@
         @SystemApi
         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
 
+        /**
+         * {@code SemanticAction}: Mark content as a potential phishing attempt.
+         * Note that this is only for use by the notification assistant services.
+         * @hide
+         */
+        @SystemApi
+        public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
+
         private final Bundle mExtras;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         private Icon mIcon;
@@ -2315,7 +2323,8 @@
                 SEMANTIC_ACTION_THUMBS_UP,
                 SEMANTIC_ACTION_THUMBS_DOWN,
                 SEMANTIC_ACTION_CALL,
-                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
+                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
+                SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface SemanticAction {}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d5e9570..c047fc2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -128,11 +128,13 @@
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.IVpnManager;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
 import android.net.NetworkWatchlistManager;
 import android.net.TetheringManager;
+import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -384,6 +386,15 @@
                         ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE));
             }});
 
+        registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
+                new CachedServiceFetcher<VpnManager>() {
+            @Override
+            public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
+                IVpnManager service = IVpnManager.Stub.asInterface(b);
+                return new VpnManager(ctx, service);
+            }});
+
         registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class,
                 new CachedServiceFetcher<VcnManager>() {
             @Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 938ce0d..9019ddf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -343,7 +343,9 @@
                 // TopActivityToken and bounds are important if top activity is in size compat
                 && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
                 && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
-                    .equals(that.configuration.windowConfiguration.getBounds()));
+                    .equals(that.configuration.windowConfiguration.getBounds()))
+                && (!topActivityInSizeCompat || configuration.getLayoutDirection()
+                    == that.configuration.getLayoutDirection());
     }
 
     /**
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index e1c262c..9b99ab8 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -477,11 +477,13 @@
      * Changes to night mode take effect globally and will result in a configuration change
      * (and potentially an Activity lifecycle event) being applied to all running apps.
      * Developers interested in an app-local implementation of night mode should consider using
-     * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} to manage the
-     * -night qualifier locally.
+     * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
+     * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+     * backward compatible implementation.
      *
      * @param mode the night mode to set
      * @see #getNightMode()
+     * @see #setApplicationNightMode(int)
      */
     public void setNightMode(@NightMode int mode) {
         if (mService != null) {
@@ -494,6 +496,44 @@
     }
 
     /**
+     * Sets and persist the night mode for this application.
+     * <p>
+     * The mode can be one of:
+     * <ul>
+     *   <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+     *       {@code notnight} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
+     *       {@code night} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the custom time set (or default)</li>
+     *   <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the device's current
+     *       location and certain other sensors</li>
+     * </ul>
+     * <p>
+     * Changes to night mode take effect locally and will result in a configuration change
+     * (and potentially an Activity lifecycle event) being applied to this application. The mode
+     * is persisted for this application until it is either modified by the application, the
+     * user clears the data for the application, or this application is uninstalled.
+     * <p>
+     * Developers interested in a non-persistent app-local implementation of night mode should
+     * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+     * to manage the -night qualifier locally.
+     *
+     * @param mode the night mode to set
+     * @see #setNightMode(int)
+     */
+    public void setApplicationNightMode(@NightMode int mode) {
+        if (mService != null) {
+            try {
+                mService.setApplicationNightMode(mode);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Returns the currently configured night mode.
      * <p>
      * May be one of:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff6f6a0..3b80f83 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1300,10 +1300,11 @@
      * A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is
      * organization-owned.
      *
-     * <p>Using this value will cause the admin app's {@link #ACTION_GET_PROVISIONING_MODE}
-     * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
-     * contain {@link #PROVISIONING_MODE_MANAGED_PROFILE} and {@link
-     * #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
+     * <p>Using this value indicates the admin app can only be provisioned in either a
+     * fully-managed device or a corporate-owned work profile. This will cause the admin app's
+     * {@link #ACTION_GET_PROVISIONING_MODE} activity to have the {@link
+     * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
+     * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
      *
      * <p>Also, if this value is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
      * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
@@ -11717,8 +11718,11 @@
     }
 
     /**
-     * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
-     * control the network logging feature.
+     * Called by a device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
      *     functions are recorded while network logging is active:
@@ -11758,7 +11762,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
      * @param enabled whether network logging should be enabled or not.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner.
      * @see #setAffiliationIds
      * @see #retrieveNetworkLogs
      */
@@ -11772,14 +11776,16 @@
     }
 
     /**
-     * Return whether network logging is enabled by a device owner.
+     * Return whether network logging is enabled by a device owner or profile owner of
+     * a managed profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
      * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
      * or has MANAGE_USERS permission.
-     * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner and caller has
-     * no MANAGE_USERS permission
+     * @return {@code true} if network logging is enabled by device owner or profile owner,
+     * {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner and
+     * caller has no MANAGE_USERS permission
      */
     public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isNetworkLoggingEnabled");
@@ -11791,9 +11797,14 @@
     }
 
     /**
-     * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
-     * the most recent batch of network logging events.
-     * A device owner has to provide a batchToken provided as part of
+     * Called by device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to retrieve the most recent batch of
+     * network logging events.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
+     *
+     * A device owner or profile owner has to provide a batchToken provided as part of
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
      * token of the most recent available batch of logs, {@code null} will be returned.
      *
@@ -11805,11 +11816,11 @@
      * after the device device owner has been notified via
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
      *
-     * <p>If a secondary user or profile is created, calling this method will throw a
-     * {@link SecurityException} until all users become affiliated again. It will also no longer be
-     * possible to retrieve the network logs batch with the most recent batchToken provided
-     * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
-     * {@link DevicePolicyManager#setAffiliationIds}.
+     * <p>If the caller is not a profile owner and a secondary user or profile is created, calling
+     * this method will throw a {@link SecurityException} until all users become affiliated again.
+     * It will also no longer be possible to retrieve the network logs batch with the most recent
+     * batchToken provided by {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
+     * See {@link DevicePolicyManager#setAffiliationIds}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
@@ -11817,8 +11828,9 @@
      * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
      *        {@code null} if the batch represented by batchToken is no longer available or if
      *        logging is disabled.
-     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
-     * profile or secondary user that is not affiliated with the device.
+     * @throws SecurityException if {@code admin} is not a device owner, profile owner or if the
+     * {@code admin} is not a profile owner and there is at least one profile or secondary user
+     * that is not affiliated with the device.
      * @see #setAffiliationIds
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
      */
@@ -11937,11 +11949,12 @@
     }
 
     /**
-     * Called by the system to get the time at which the device owner last retrieved network logging
-     * events.
+     * Called by the system to get the time at which the device owner or profile owner of a
+     * managed profile last retrieved network logging events.
      *
-     * @return the time at which the device owner most recently retrieved network logging events, in
-     *         milliseconds since epoch; -1 if network logging events were never retrieved.
+     * @return the time at which the device owner or profile owner most recently retrieved network
+     *         logging events, in milliseconds since epoch; -1 if network logging events were
+     *         never retrieved.
      * @throws SecurityException if the caller is not the device owner, does not hold the
      *         MANAGE_USERS permission and is not the system.
      *
@@ -13357,24 +13370,11 @@
      */
     public boolean canAdminGrantSensorsPermissions() {
         throwIfParentInstance("canAdminGrantSensorsPermissions");
-        return canAdminGrantSensorsPermissionsForUser(myUserId());
-    }
-
-    /**
-     * Returns true if the admin can control grants of sensors-related permissions, for
-     * a given user.
-     *
-     * @hide
-     * @param userId The ID of the user to check.
-     * @return if the admin may grant these permissions, false otherwise.
-     */
-    @SystemApi
-    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
         if (mService == null) {
             return false;
         }
         try {
-            return mService.canAdminGrantSensorsPermissionsForUser(userId);
+            return mService.canAdminGrantSensorsPermissionsForUser(myUserId());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 7e232ac..85cfe83 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -401,7 +401,8 @@
      * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
-        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
+        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
+                mOperationType);
         if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
             return;
         }
@@ -624,7 +625,8 @@
         if (includeMap == null || includeMap.size() == 0) {
             // Do entire sub-tree for the provided token.
             fullBackupFileTree(packageName, domainToken,
-                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
+                    FullBackup.getBackupScheme(this, mOperationType)
+                            .tokenToDirectoryPath(domainToken),
                     filterSet, traversalExcludeSet, data);
         } else if (includeMap.get(domainToken) != null) {
             // This will be null if the xml parsing didn't yield any rules for
@@ -795,7 +797,8 @@
                                             ArraySet<String> systemExcludes,
             FullBackupDataOutput output) {
         // Pull out the domain and set it aside to use when making the tarball.
-        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+        String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+                .tokenToDirectoryPath(domain);
         if (domainPath == null) {
             // Should never happen.
             return;
@@ -911,7 +914,7 @@
             return true;
         }
 
-        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
+        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
         if (!bs.isFullBackupContentEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
@@ -985,7 +988,8 @@
                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
                 + " mtime=" + mtime);
 
-        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+        basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+                domain);
         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
             mode = -1;  // < 0 is a token to skip attempting a chmod()
         }
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 742d05c..f7ed6f1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,9 @@
 
 package android.app.backup;
 
+import static android.app.backup.BackupManager.OperationType;
+
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -41,6 +44,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -90,27 +94,61 @@
             "fakeClientSideEncryption";
 
     /**
+     * Identify {@link BackupScheme} object by package and operation type
+     * (see {@link OperationType}) it corresponds to.
+     */
+    private static class BackupSchemeId {
+        final String mPackageName;
+        @OperationType final int mOperationType;
+
+        BackupSchemeId(String packageName, @OperationType int operationType) {
+            mPackageName = packageName;
+            mOperationType = operationType;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mOperationType);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object == null || getClass() != object.getClass()) {
+                return false;
+            }
+            BackupSchemeId that = (BackupSchemeId) object;
+            return Objects.equals(mPackageName, that.mPackageName) &&
+                    Objects.equals(mOperationType, that.mOperationType);
+        }
+    }
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
     static public native int backupToTar(String packageName, String domain,
             String linkdomain, String rootpath, String path, FullBackupDataOutput output);
 
-    private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
-            new ArrayMap<String, BackupScheme>();
+    private static final Map<BackupSchemeId, BackupScheme> kPackageBackupSchemeMap =
+            new ArrayMap<>();
 
-    static synchronized BackupScheme getBackupScheme(Context context) {
+    static synchronized BackupScheme getBackupScheme(Context context,
+            @OperationType int operationType) {
+        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
         BackupScheme backupSchemeForPackage =
-                kPackageBackupSchemeMap.get(context.getPackageName());
+                kPackageBackupSchemeMap.get(backupSchemeId);
         if (backupSchemeForPackage == null) {
-            backupSchemeForPackage = new BackupScheme(context);
-            kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
+            backupSchemeForPackage = new BackupScheme(context, operationType);
+            kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
         }
         return backupSchemeForPackage;
     }
 
     public static BackupScheme getBackupSchemeForTest(Context context) {
-        BackupScheme testing = new BackupScheme(context);
+        BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
         testing.mExcludes = new ArraySet();
         testing.mIncludes = new ArrayMap();
         return testing;
@@ -236,6 +274,7 @@
         private final static String TAG_EXCLUDE = "exclude";
 
         final int mFullBackupContent;
+        @OperationType final int mOperationType;
         final PackageManager mPackageManager;
         final StorageManager mStorageManager;
         final String mPackageName;
@@ -354,8 +393,9 @@
          */
         ArraySet<PathWithRequiredFlags> mExcludes;
 
-        BackupScheme(Context context) {
+        BackupScheme(Context context, @OperationType int operationType) {
             mFullBackupContent = context.getApplicationInfo().fullBackupContent;
+            mOperationType = operationType;
             mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 28b7340..ab38832 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -20,8 +20,16 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.compat.Compatibility;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.IPlatformCompat;
+
+import java.util.Map;
+
 /**
  * CompatChanges APIs - to be used by platform code only (including mainline
  * modules).
@@ -89,4 +97,25 @@
         return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
     }
 
+    /**
+     * Set an app compat override for a given package. This will check whether the caller is allowed
+     * to perform this operation on the given apk and build. Only the installer package is allowed
+     * to set overrides on a non-debuggable final build and a non-test apk.
+     *
+     * @param packageName The package name of the app in question.
+     * @param overrides A map from changeId to the override applied for this change id.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG)
+    public static void setPackageOverride(String packageName,
+            Map<Long, PackageOverride> overrides) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
+        try {
+            platformCompat.setOverridesFromInstaller(config, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
new file mode 100644
index 0000000..9f97cd4
--- /dev/null
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -0,0 +1,211 @@
+/*
+ * 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.compat;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An app compat override applied to a given package and change id pairing.
+ *
+ * A package override contains a list of version ranges with the desired boolean value of
+ * the override for the app in this version range. Ranges can be open ended in either direction.
+ * An instance of PackageOverride gets created via {@link Builder} and is immutable once created.
+ *
+ * @hide
+ */
+public class PackageOverride implements Parcelable {
+
+    @IntDef({
+            VALUE_UNDEFINED,
+            VALUE_ENABLED,
+            VALUE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
+    public @interface EvaluatedOverride {
+    }
+
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * this PackageOverride does not define the value of the override for the given version.
+     * @hide
+     */
+    public static final int VALUE_UNDEFINED = 0;
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * the override evaluates to {@code true} for the given version.
+     * @hide
+     */
+    public static final int VALUE_ENABLED = 1;
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * the override evaluates to {@code fakse} for the given version.
+     * @hide
+     */
+    public static final int VALUE_DISABLED = 2;
+
+    private final long mMinVersionCode;
+    private final long mMaxVersionCode;
+    private final boolean mEnabled;
+
+    private PackageOverride(long minVersionCode,
+            long maxVersionCode,
+            boolean enabled) {
+        this.mMinVersionCode = minVersionCode;
+        this.mMaxVersionCode = maxVersionCode;
+        this.mEnabled = enabled;
+    }
+
+    private PackageOverride(Parcel in) {
+        this(in.readLong(), in.readLong(), in.readBoolean());
+    }
+
+    /**
+     * Evaluate the override for the given {@code versionCode}. If no override is defined for
+     * the specified version code, {@link #VALUE_UNDEFINED} is returned.
+     * @hide
+     */
+    public @EvaluatedOverride int evaluate(long versionCode) {
+        if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) {
+            return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+        }
+        return VALUE_UNDEFINED;
+    }
+
+    /**
+     * Evaluate the override independent of version code, i.e. only return an evaluated value if
+     * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned.
+     * @hide
+     */
+    public int evaluateForAllVersions() {
+        if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+            return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+        }
+        return VALUE_UNDEFINED;
+    }
+
+    /** Returns the minimum version code the override applies to. */
+    public long getMinVersionCode() {
+        return mMinVersionCode;
+    }
+
+    /** Returns the minimum version code the override applies from. */
+    public long getMaxVersionCode() {
+        return mMaxVersionCode;
+    }
+
+    /** Returns the enabled value for the override. */
+    public boolean getEnabled() {
+        return mEnabled;
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mMinVersionCode);
+        dest.writeLong(mMaxVersionCode);
+        dest.writeBoolean(mEnabled);
+    }
+
+    /** @hide */
+    @Override
+    public String toString() {
+        if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+            return Boolean.toString(mEnabled);
+        }
+        return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled);
+    }
+
+    /** @hide */
+    public static final Creator<PackageOverride> CREATOR =
+            new Creator<PackageOverride>() {
+
+                @Override
+                public PackageOverride createFromParcel(Parcel in) {
+                    return new PackageOverride(in);
+                }
+
+                @Override
+                public PackageOverride[] newArray(int size) {
+                    return new PackageOverride[size];
+                }
+            };
+
+    /**
+     * Builder to construct a PackageOverride.
+     */
+    public static class Builder {
+        private long mMinVersionCode = Long.MIN_VALUE;
+        private long mMaxVersionCode = Long.MAX_VALUE;
+        private boolean mEnabled;
+
+        /**
+         * Sets the minimum version code the override should apply from.
+         *
+         * default value: {@code Long.MIN_VALUE}.
+         */
+        public Builder setMinVersionCode(long minVersionCode) {
+            mMinVersionCode = minVersionCode;
+            return this;
+        }
+
+        /**
+         * Sets the maximum version code the override should apply to.
+         *
+         * default value: {@code Long.MAX_VALUE}.
+         */
+        public Builder setMaxVersionCode(long maxVersionCode) {
+            mMaxVersionCode = maxVersionCode;
+            return this;
+        }
+
+        /**
+         * Sets whether the override should be enabled for the given version range.
+         *
+         * default value: {@code false}.
+         */
+        public Builder setEnabled(boolean enabled) {
+            mEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Build the {@link PackageOverride}.
+         *
+         * @throws IllegalArgumentException if {@code minVersionCode} is larger than
+         *                                  {@code maxVersionCode}.
+         */
+        public PackageOverride build() {
+            if (mMinVersionCode > mMaxVersionCode) {
+                throw new IllegalArgumentException("minVersionCode must not be larger than "
+                        + "maxVersionCode");
+            }
+            return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled);
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d451599..e6fdc00 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -60,7 +60,7 @@
     public void postExecute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         // TODO(lifecycler): Use interface callback instead of actual implementation.
-        ActivityClient.getInstance().activityResumed(token);
+        ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
new file mode 100644
index 0000000..5374984
--- /dev/null
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -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 android.app.servertransaction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Transfer a splash screen view to an Activity.
+ * @hide
+ */
+public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
+
+    private SplashScreenViewParcelable mSplashScreenViewParcelable;
+    private @TransferRequest int mRequest;
+
+    @IntDef(value = {
+            ATTACH_TO,
+            HANDOVER_TO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransferRequest {}
+    // request client to attach the view on it.
+    public static final int ATTACH_TO = 0;
+    // tell client that you can handle the splash screen view.
+    public static final int HANDOVER_TO = 1;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull ActivityThread.ActivityClientRecord r,
+            PendingTransactionActions pendingActions) {
+        switch (mRequest) {
+            case ATTACH_TO:
+                client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
+                break;
+            case HANDOVER_TO:
+                client.handOverSplashScreenView(r);
+                break;
+        }
+    }
+
+    @Override
+    public void recycle() {
+        ObjectPool.recycle(this);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequest);
+        dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+    }
+
+    private TransferSplashScreenViewStateItem() {}
+    private TransferSplashScreenViewStateItem(Parcel in) {
+        mRequest = in.readInt();
+        mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+    }
+
+    /** Obtain an instance initialized with provided params. */
+    public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
+            @Nullable SplashScreenViewParcelable parcelable) {
+        TransferSplashScreenViewStateItem instance =
+                ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
+        if (instance == null) {
+            instance = new TransferSplashScreenViewStateItem();
+        }
+        instance.mRequest = state;
+        instance.mSplashScreenViewParcelable = parcelable;
+
+        return instance;
+    }
+
+    public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
+            new Creator<TransferSplashScreenViewStateItem>() {
+                public TransferSplashScreenViewStateItem createFromParcel(Parcel in) {
+                    return new TransferSplashScreenViewStateItem(in);
+                }
+
+                public TransferSplashScreenViewStateItem[] newArray(int size) {
+                    return new TransferSplashScreenViewStateItem[size];
+                }
+            };
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 0188637..f3ecbf6 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -740,6 +740,7 @@
         mIcon = null;
         mItems = new ArrayList<Item>();
         mItems.add(item);
+        mClipDescription.setIsStyledText(isStyledText());
     }
 
     /**
@@ -756,6 +757,7 @@
         mIcon = null;
         mItems = new ArrayList<Item>();
         mItems.add(item);
+        mClipDescription.setIsStyledText(isStyledText());
     }
 
     /**
@@ -914,6 +916,9 @@
             throw new NullPointerException("item is null");
         }
         mItems.add(item);
+        if (mItems.size() == 1) {
+            mClipDescription.setIsStyledText(isStyledText());
+        }
     }
 
     /**
@@ -1049,6 +1054,20 @@
         }
     }
 
+    private boolean isStyledText() {
+        if (mItems.isEmpty()) {
+            return false;
+        }
+        final CharSequence text = mItems.get(0).getText();
+        if (text instanceof Spanned) {
+            Spanned spanned = (Spanned) text;
+            if (TextUtils.hasStyleSpan(spanned)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index e3395e2..d48f832 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -120,6 +120,7 @@
     private final ArrayList<String> mMimeTypes;
     private PersistableBundle mExtras;
     private long mTimeStamp;
+    private boolean mIsStyledText;
 
     /**
      * Create a new clip.
@@ -325,6 +326,26 @@
         }
     }
 
+    /**
+     * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e.
+     * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link
+     * android.text.style.ParagraphStyle ParagraphStyle}, or {@link
+     * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if
+     * there is no associated clip data.
+     */
+    public boolean isStyledText() {
+        return mIsStyledText;
+    }
+
+    /**
+     * Sets whether the associated {@link ClipData} contains styled text in its first item. This
+     * should be called when this description is associated with clip data or when the first item
+     * is added to the associated clip data.
+     */
+    void setIsStyledText(boolean isStyledText) {
+        mIsStyledText = isStyledText;
+    }
+
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
@@ -429,6 +450,7 @@
         dest.writeStringList(mMimeTypes);
         dest.writePersistableBundle(mExtras);
         dest.writeLong(mTimeStamp);
+        dest.writeBoolean(mIsStyledText);
     }
 
     ClipDescription(Parcel in) {
@@ -436,6 +458,7 @@
         mMimeTypes = in.createStringArrayList();
         mExtras = in.readPersistableBundle();
         mTimeStamp = in.readLong();
+        mIsStyledText = in.readBoolean();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 025d777..24b7629 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -93,6 +93,7 @@
 import java.io.InputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -5725,6 +5726,32 @@
     public abstract int checkUriPermission(Uri uri, int pid, int uid,
             @Intent.AccessUriMode int modeFlags);
 
+    /**
+     * Determine whether a particular process and user ID has been granted
+     * permission to access a list of URIs.  This only checks for permissions
+     * that have been explicitly granted -- if the given process/uid has
+     * more general access to the URI's content provider then this check will
+     * always fail.
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The access modes to check for the list of uris
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
     /** @hide */
     @SuppressWarnings("HiddenAbstractMethod")
     @PackageManager.PermissionResult
@@ -5755,6 +5782,32 @@
     public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
 
     /**
+     * Determine whether the calling process and user ID has been
+     * granted permission to access a list of URIs.  This is basically
+     * the same as calling {@link #checkUriPermissions(List, int, int, int)}
+     * with the pid and uid returned by {@link
+     * android.os.Binder#getCallingPid} and {@link
+     * android.os.Binder#getCallingUid}.  One important difference is
+     * that if you are not currently processing an IPC, this function
+     * will always fail.
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param modeFlags The access modes to check.
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkUriPermission(Uri, int, int, int)
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Determine whether the calling process of an IPC <em>or you</em> has been granted
      * permission to access a specific URI.  This is the same as
      * {@link #checkCallingUriPermission}, except it grants your own permissions
@@ -5775,6 +5828,28 @@
             @Intent.AccessUriMode int modeFlags);
 
     /**
+     * Determine whether the calling process of an IPC <em>or you</em> has been granted
+     * permission to access a list of URIs.  This is the same as
+     * {@link #checkCallingUriPermission}, except it grants your own permissions
+     * if you are not currently processing an IPC.  Use with care!
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param modeFlags The access modes to check.
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Check both a Uri and normal permission.  This allows you to perform
      * both {@link #checkPermission} and {@link #checkUriPermission} in one
      * call.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c1c213e..b71fb27 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -53,6 +53,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -905,6 +906,13 @@
         return mBase.checkUriPermission(uri, pid, uid, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            int modeFlags) {
+        return mBase.checkUriPermissions(uris, pid, uid, modeFlags);
+    }
+
     /** @hide */
     @Override
     public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -916,11 +924,23 @@
         return mBase.checkCallingUriPermission(uri, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return mBase.checkCallingUriPermissions(uris, modeFlags);
+    }
+
     @Override
     public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
         return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return mBase.checkCallingOrSelfUriPermissions(uris, modeFlags);
+    }
+
     @Override
     public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
             @Nullable String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 85549d8..ebe202b 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -273,7 +273,7 @@
                 text, 0, null, disabledMessage, 0, null,
                 categoriesSet, intents, rank, extras,
                 getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons, locusId);
+                disabledReason, persons, locusId, 0);
     }
 
     /** @hide */
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a2e533a..0e70a3e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -28,6 +28,9 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Set;
@@ -477,6 +480,8 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
     /**
      * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
      * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
@@ -688,6 +693,7 @@
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+        sForStringSet.parcel(knownCerts, dest, parcelableFlags);
     }
 
     /** @hide */
@@ -753,5 +759,6 @@
         descriptionRes = source.readInt();
         requestRes = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        knownCerts = sForStringSet.unparcel(source);
     }
 }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 522f4ca..ce0547f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -434,6 +434,8 @@
 
     private int mDisabledReason;
 
+    private int mStartingThemeResId;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -462,6 +464,7 @@
         mLocusId = b.mLocusId;
 
         updateTimestamp();
+        mStartingThemeResId = b.mStartingThemeResId;
     }
 
     /**
@@ -608,6 +611,7 @@
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
         }
+        mStartingThemeResId = source.mStartingThemeResId;
     }
 
     /**
@@ -931,6 +935,9 @@
         if (source.mLocusId != null) {
             mLocusId = source.mLocusId;
         }
+        if (source.mStartingThemeResId != 0) {
+            mStartingThemeResId = source.mStartingThemeResId;
+        }
     }
 
     /**
@@ -1000,6 +1007,8 @@
 
         private LocusId mLocusId;
 
+        private int mStartingThemeResId;
+
         /**
          * Old style constructor.
          * @hide
@@ -1102,6 +1111,15 @@
         }
 
         /**
+         * Sets a theme resource id for the splash screen.
+         */
+        @NonNull
+        public Builder setStartingTheme(int themeResId) {
+            mStartingThemeResId = themeResId;
+            return this;
+        }
+
+        /**
          * @hide We don't support resource strings for dynamic shortcuts for now.  (But unit tests
          * use it.)
          */
@@ -1420,6 +1438,14 @@
         return mIcon;
     }
 
+    /**
+     * Returns the theme resource id used for the splash screen.
+     * @hide
+     */
+    public int getStartingThemeResId() {
+        return mStartingThemeResId;
+    }
+
     /** @hide -- old signature, the internal code still uses it. */
     @Nullable
     @Deprecated
@@ -2138,6 +2164,7 @@
         mPersons = source.readParcelableArray(cl, Person.class);
         mLocusId = source.readParcelable(cl);
         mIconUri = source.readString8();
+        mStartingThemeResId = source.readInt();
     }
 
     @Override
@@ -2189,6 +2216,7 @@
         dest.writeParcelableArray(mPersons, flags);
         dest.writeParcelable(mLocusId, flags);
         dest.writeString8(mIconUri);
+        dest.writeInt(mStartingThemeResId);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2345,6 +2373,12 @@
         sb.append("disabledReason=");
         sb.append(getDisabledReasonDebugString(mDisabledReason));
 
+        if (mStartingThemeResId != 0) {
+            addIndentOrComma(sb, indent);
+            sb.append("SplashScreenThemeResId=");
+            sb.append(Integer.toHexString(mStartingThemeResId));
+        }
+
         addIndentOrComma(sb, indent);
 
         sb.append("categories=");
@@ -2430,7 +2464,7 @@
             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
             long lastChangedTimestamp,
             int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
-            int disabledReason, Person[] persons, LocusId locusId) {
+            int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -2459,5 +2493,6 @@
         mDisabledReason = disabledReason;
         mPersons = persons;
         mLocusId = locusId;
+        mStartingThemeResId = startingThemeResId;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index c62767e..233abf3 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -71,6 +71,13 @@
     public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
+    /**
+     * Get the theme res ID of the starting window, it can be 0 if not specified.
+     */
+    public abstract int getShortcutStartingThemeResId(int launcherUserId,
+            @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
+            int userId);
+
     public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
             @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index bf35c4d..bc5d14a 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -22,17 +22,26 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.jar.StrictJarFile;
+import android.util.JsonReader;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.zip.ZipEntry;
 
 /**
  * Helper class used to compute and validate the location of dex metadata files.
@@ -40,6 +49,12 @@
  * @hide
  */
 public class DexMetadataHelper {
+    public static final String TAG = "DexMetadataHelper";
+    /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */
+    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */
+    private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest";
+
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
 
     private DexMetadataHelper() {}
@@ -147,14 +162,31 @@
 
     /**
      * Validate that the given file is a dex metadata archive.
-     * This is just a validation that the file is a zip archive.
+     * This is just a validation that the file is a zip archive that contains a manifest.json
+     * with the package name and version code.
      *
      * @throws PackageParserException if the file is not a .dm file.
      */
-    public static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+    public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode)
+            throws PackageParserException {
+        validateDexMetadataFile(dmaPath, packageName, versionCode,
+               SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false));
+    }
+
+    @VisibleForTesting
+    public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode,
+            boolean requireManifest) throws PackageParserException {
         StrictJarFile jarFile = null;
+
+        if (DEBUG) {
+            Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName +
+                    ", " + versionCode);
+        }
+
         try {
             jarFile = new StrictJarFile(dmaPath, false, false);
+            validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode,
+                    requireManifest);
         } catch (IOException e) {
             throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
                     "Error opening " + dmaPath, e);
@@ -168,6 +200,72 @@
         }
     }
 
+    /** Ensure that packageName and versionCode match the manifest.json in the .dm file */
+    private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile,
+            String packageName, long versionCode, boolean requireManifest)
+            throws IOException, PackageParserException {
+        if (!requireManifest) {
+            if (DEBUG) {
+                Log.v(TAG, "validateDexMetadataManifest: " + dmaPath
+                        + " manifest.json check skipped");
+            }
+            return;
+        }
+
+        ZipEntry zipEntry = jarFile.findEntry("manifest.json");
+        if (zipEntry == null) {
+              throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                      "Missing manifest.json in " + dmaPath);
+        }
+        InputStream inputStream = jarFile.getInputStream(zipEntry);
+
+        JsonReader reader;
+        try {
+          reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "Error opening manifest.json in " + dmaPath, e);
+        }
+        String jsonPackageName = null;
+        long jsonVersionCode = -1;
+
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (name.equals("packageName")) {
+                jsonPackageName = reader.nextString();
+            } else if (name.equals("versionCode")) {
+                jsonVersionCode = reader.nextLong();
+            } else {
+                reader.skipValue();
+            }
+        }
+        reader.endObject();
+
+        if (jsonPackageName == null || jsonVersionCode == -1) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath
+                    + " is missing 'packageName' and/or 'versionCode'");
+        }
+
+        if (!jsonPackageName.equals(packageName)) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName
+                    + ", expected: " + packageName);
+        }
+
+        if (versionCode != jsonVersionCode) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode
+                    + ", expected: " + versionCode);
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName +
+                    ", " + versionCode + ": successful");
+        }
+    }
+
     /**
      * Validates that all dex metadata paths in the given list have a matching apk.
      * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index 35bb33c..37e0e87 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -21,16 +21,22 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
 
+import java.util.Locale;
 import java.util.Set;
 
 /** @hide */
 public class ParsedPermission extends ParsedComponent {
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
     @Nullable
     String backgroundPermission;
     @Nullable
@@ -89,6 +95,19 @@
         return knownCerts;
     }
 
+    protected void setKnownCert(String knownCert) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+    }
+
+    protected void setKnownCerts(String[] knownCerts) {
+        this.knownCerts = new ArraySet<>();
+        for (String knownCert : knownCerts) {
+            this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
+
     public int calculateFootprint() {
         int size = getName().length();
         if (getNonLocalizedLabel() != null) {
@@ -117,6 +136,7 @@
         dest.writeInt(this.protectionLevel);
         dest.writeBoolean(this.tree);
         dest.writeParcelable(this.parsedPermissionGroup, flags);
+        sForStringSet.parcel(knownCerts, dest, flags);
     }
 
     protected ParsedPermission(Parcel in) {
@@ -129,6 +149,7 @@
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
         this.parsedPermissionGroup = in.readParcelable(boot);
+        this.knownCerts = sForStringSet.unparcel(in);
     }
 
     public static final Parcelable.Creator<ParsedPermission> CREATOR =
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index a7cecbe..8afa70e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -25,7 +25,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -33,8 +32,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
 
 /** @hide */
 public class ParsedPermissionUtils {
@@ -103,17 +100,12 @@
                 if (resourceType.equals("array")) {
                     final String[] knownCerts = res.getStringArray(knownCertsResource);
                     if (knownCerts != null) {
-                        // Convert the provided digest to upper case for consistent Set membership
-                        // checks when verifying the signing certificate digests of requesting apps.
-                        permission.knownCerts = new ArraySet<>();
-                        for (String knownCert : knownCerts) {
-                            permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
-                        }
+                        permission.setKnownCerts(knownCerts);
                     }
                 } else {
                     final String knownCert = res.getString(knownCertsResource);
                     if (knownCert != null) {
-                        permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                        permission.setKnownCert(knownCert);
                     }
                 }
                 if (permission.knownCerts == null) {
@@ -126,7 +118,7 @@
                 final String knownCert = sa.getString(
                         R.styleable.AndroidManifestPermission_knownCerts);
                 if (knownCert != null) {
-                    permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                    permission.setKnownCert(knownCert);
                 }
             }
 
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index af12536..cbb3baa 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -238,8 +238,8 @@
      * permissions must be acquired and
      * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
      *
-     * This will be combined with the verification status and other system state to determine which
-     * application is launched to handle an app link.
+     * Enabling an unverified domain will allow an application to open it, but this can only occur
+     * if no other app on the device is approved for the domain.
      *
      * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
      * @param domains     The domains to toggle the state of.
@@ -290,13 +290,15 @@
         public static final int REASON_ID_INVALID = 2;
         public static final int REASON_SET_NULL_OR_EMPTY = 3;
         public static final int REASON_UNKNOWN_DOMAIN = 4;
+        public static final int REASON_UNABLE_TO_APPROVE = 5;
 
         /** @hide */
         @IntDef({
                 REASON_ID_NULL,
                 REASON_ID_INVALID,
                 REASON_SET_NULL_OR_EMPTY,
-                REASON_UNKNOWN_DOMAIN
+                REASON_UNKNOWN_DOMAIN,
+                REASON_UNABLE_TO_APPROVE
         })
         public @interface Reason {
         }
@@ -313,6 +315,8 @@
                 case REASON_UNKNOWN_DOMAIN:
                     return "Domain set contains value that was not declared by the target package "
                             + packageName;
+                case REASON_UNABLE_TO_APPROVE:
+                    return "Domain set contains value that was owned by another package";
                 default:
                     return "Unknown failure";
             }
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
index 8d16f75..73346ef 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -17,6 +17,7 @@
 package android.content.pm.verify.domain;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Parcelable;
@@ -30,18 +31,18 @@
 import java.util.UUID;
 
 /**
- * Contains the user selection state for a package. This means all web HTTP(S) domains
- * declared by a package in its manifest, whether or not they were marked for auto
- * verification.
+ * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
+ * package in its manifest, whether or not they were marked for auto verification.
  * <p>
  * By default, all apps are allowed to automatically open links with domains that they've
- * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
- * The user can decide to disable this, disallowing the application from opening these
- * links.
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
+ * can decide to disable this, disallowing the application from opening all links. Note that the
+ * toggle affects <b>all</b> links and is not based on the verification state of the domains.
  * <p>
- * Separately, independent of this toggle, the user can choose specific domains to allow
- * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
- * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
+ * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
+ * a single application can be approved for a domain unless the applications are both approved. If
+ * another application is approved, the user will not be allowed to enable the domain.
  * <p>
  * These values can be changed through the
  * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
@@ -105,7 +106,8 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+    // /DomainVerificationUserSelection.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -216,7 +218,7 @@
 
     @Override
     @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
+    public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
         // boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -328,9 +330,9 @@
     };
 
     @DataClass.Generated(
-            time = 1611799495498L,
+            time = 1612829797220L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
             inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
new file mode 100644
index 0000000..25758e9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for updating or adding a font family on the system.
+ *
+ * <p>You can update or add a font family with custom style parameters. The following example
+ * defines a font family called "roboto" using "Roboto-Regular" font file that is already available
+ * on the system by preloading or {@link FontManager#updateFontFile}.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("roboto", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()),
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * <p>You can update or add font files in the same request by calling
+ * {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+ * The following example adds "YourFont" font file and defines "your-font" font family in the same
+ * request. In this case, the font file represented by {@code yourFontFd} should be an OpenType
+ * compliant font file and have "YourFont" as PostScript name (ID=6) in 'name' table.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFileUpdateRequest(new FontFileUpdateRequest(yourFontFd, signature))
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("your-font", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "YourFont",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFamilyUpdateRequest {
+
+    /**
+     * A font family definition.
+     */
+    public static final class FontFamily {
+        @NonNull
+        private final String mName;
+        @NonNull
+        private final List<Font> mFonts;
+
+        /**
+         * Constructs a FontFamily.
+         *
+         * <p>A font family has a name to identify the font family. Apps can use
+         * {@link android.graphics.Typeface#create(String, int)} or XML resources to use a specific
+         * font family.
+         *
+         * <p>A font family consists of multiple fonts with different styles. The style information
+         * can be specified by {@link Font}.
+         *
+         * @see android.graphics.Typeface#create(String, int)
+         * @see Font
+         */
+        public FontFamily(@NonNull String name, @NonNull List<Font> fonts) {
+            Objects.requireNonNull(name);
+            Preconditions.checkStringNotEmpty(name);
+            Objects.requireNonNull(fonts);
+            Preconditions.checkCollectionElementsNotNull(fonts, "fonts");
+            Preconditions.checkCollectionNotEmpty(fonts, "fonts");
+            mName = name;
+            mFonts = fonts;
+        }
+
+        /**
+         * Returns the name of this family.
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the fonts in this family.
+         */
+        @NonNull
+        public List<Font> getFonts() {
+            return mFonts;
+        }
+    }
+
+    /**
+     * A single entry in a font family representing a font.
+     */
+    public static final class Font {
+
+        @NonNull
+        private final String mPostScriptName;
+        @NonNull
+        private final FontStyle mStyle;
+        @NonNull
+        private final List<FontVariationAxis> mAxes;
+
+        /**
+         * Constructs a FontStyleVariation.
+         *
+         * <p>A font has a PostScript name to identify the font file to use, a {@link FontStyle}
+         * to specify the style, and a list of {@link FontVariationAxis} to specify axis tags and
+         * values for variable fonts. If the font file identified by {@code postScriptName} is not a
+         * variable font, {@code axes} must be empty.
+         *
+         * @param postScriptName The PostScript name of the font file to use. PostScript name is in
+         *                       Name ID 6 field in 'name' table, as specified by OpenType
+         *                       specification.
+         * @param style          The style for this font.
+         * @param axes           A list of {@link FontVariationAxis} to specify axis tags and values
+         *                       for variable fonts.
+         */
+        public Font(@NonNull String postScriptName, @NonNull FontStyle style,
+                @NonNull List<FontVariationAxis> axes) {
+            Objects.requireNonNull(postScriptName);
+            Preconditions.checkStringNotEmpty(postScriptName);
+            Objects.requireNonNull(style);
+            Objects.requireNonNull(axes);
+            Preconditions.checkCollectionElementsNotNull(axes, "axes");
+            mPostScriptName = postScriptName;
+            mStyle = style;
+            mAxes = axes;
+        }
+
+        /**
+         * Returns PostScript name of the font file to use.
+         */
+        @NonNull
+        public String getPostScriptName() {
+            return mPostScriptName;
+        }
+
+        /**
+         * Returns the style.
+         */
+        @NonNull
+        public FontStyle getStyle() {
+            return mStyle;
+        }
+
+        /**
+         * Returns the list of {@link FontVariationAxis}.
+         */
+        @NonNull
+        public List<FontVariationAxis> getAxes() {
+            return mAxes;
+        }
+    }
+
+    /**
+     * Builds a {@link FontFamilyUpdateRequest}.
+     */
+    public static final class Builder {
+        @NonNull
+        private final List<FontFileUpdateRequest> mFontFileUpdateRequests = new ArrayList<>();
+        @NonNull
+        private final List<FontFamily> mFontFamilies = new ArrayList<>();
+
+        /**
+         * Constructs a FontFamilyUpdateRequest.Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Adds a {@link FontFileUpdateRequest} to execute as a part of the constructed
+         * {@link FontFamilyUpdateRequest}.
+         *
+         * @param request A font file update request.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFileUpdateRequest(@NonNull FontFileUpdateRequest request) {
+            Objects.requireNonNull(request);
+            mFontFileUpdateRequests.add(request);
+            return this;
+        }
+
+        /**
+         * Adds a font family to update an existing font family in the system font config or
+         * add as a new font family to the system font config.
+         *
+         * @param fontFamily An font family definition to add or update.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFamily(@NonNull FontFamily fontFamily) {
+            Objects.requireNonNull(fontFamily);
+            mFontFamilies.add(fontFamily);
+            return this;
+        }
+
+        /**
+         * Builds a {@link FontFamilyUpdateRequest}.
+         */
+        @NonNull
+        public FontFamilyUpdateRequest build() {
+            return new FontFamilyUpdateRequest(mFontFileUpdateRequests, mFontFamilies);
+        }
+    }
+
+    @NonNull
+    private final List<FontFileUpdateRequest> mFontFiles;
+
+    @NonNull
+    private final List<FontFamily> mFontFamilies;
+
+    private FontFamilyUpdateRequest(@NonNull List<FontFileUpdateRequest> fontFiles,
+            @NonNull List<FontFamily> fontFamilies) {
+        mFontFiles = fontFiles;
+        mFontFamilies = fontFamilies;
+    }
+
+    /**
+     * Returns the list of {@link FontFileUpdateRequest} that will be executed as a part of this
+     * request.
+     */
+    @NonNull
+    public List<FontFileUpdateRequest> getFontFileUpdateRequests() {
+        return mFontFiles;
+    }
+
+    /**
+     * Returns the list of {@link FontFamily} that will be updated in this request.
+     */
+    @NonNull
+    public List<FontFamily> getFontFamilies() {
+        return mFontFamilies;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
new file mode 100644
index 0000000..cf1dca9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Objects;
+
+/**
+ * Request for updating a font file on the system.
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFileUpdateRequest {
+
+    private final ParcelFileDescriptor mParcelFileDescriptor;
+    private final byte[] mSignature;
+
+    /**
+     * Creates a FontFileUpdateRequest with the given file and signature.
+     *
+     * @param parcelFileDescriptor A file descriptor of the font file.
+     * @param signature            A PKCS#7 detached signature for verifying the font file.
+     */
+    public FontFileUpdateRequest(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @NonNull byte[] signature) {
+        Objects.requireNonNull(parcelFileDescriptor);
+        Objects.requireNonNull(signature);
+        mParcelFileDescriptor = parcelFileDescriptor;
+        mSignature = signature;
+    }
+
+    /**
+     * Returns the file descriptor of the font file.
+     */
+    @NonNull
+    public ParcelFileDescriptor getParcelFileDescriptor() {
+        return mParcelFileDescriptor;
+    }
+
+    /**
+     * Returns the PKCS#7 detached signature for verifying the font file.
+     */
+    @NonNull
+    public byte[] getSignature() {
+        return mSignature;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index abb4f9f..f087dd0 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -35,6 +35,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -58,7 +60,7 @@
                     RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH,
                     RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME,
                     RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG,
-                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION })
+                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_FONT_NOT_FOUND })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -131,9 +133,10 @@
     public static final int RESULT_ERROR_VERSION_MISMATCH = -8;
 
     /**
-     * Indicates a failure due to IPC communication.
+     * Indicates a failure occurred because a font with the specified PostScript name could not be
+     * found.
      */
-    public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9;
+    public static final int RESULT_ERROR_FONT_NOT_FOUND = -9;
 
     /**
      * Indicates a failure of opening font file.
@@ -253,7 +256,6 @@
      * @see #RESULT_ERROR_DOWNGRADING
      * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
      * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
-     * @see #RESULT_ERROR_REMOTE_EXCEPTION
      */
     @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
             @NonNull ParcelFileDescriptor pfd,
@@ -261,10 +263,69 @@
             @IntRange(from = 0) int baseVersion
     ) {
         try {
-            return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+            return mIFontManager.updateFontFile(new FontUpdateRequest(pfd, signature), baseVersion);
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to call updateFont API", e);
-            return RESULT_ERROR_REMOTE_EXCEPTION;
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Update or add system wide font families.
+     *
+     * <p>This method will update existing font families or add new font families. The updated
+     * font family definitions will be used when creating {@link android.graphics.Typeface} objects
+     * with using {@link android.graphics.Typeface#create(String, int)} specifying the family name,
+     * or through XML resources. Note that system fallback fonts cannot be modified by this method.
+     * Apps must use {@link android.graphics.Typeface.CustomFallbackBuilder} to use custom fallback
+     * fonts.
+     *
+     * <p>Font files can be updated by including {@link FontFileUpdateRequest} to {@code request}
+     * via {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+     * The same constraints as {@link #updateFontFile} will apply when updating font files.
+     *
+     * <p>The caller must specify the base config version for keeping the font configuration
+     * consistent. If the font configuration is updated for some reason between the time you get
+     * a configuration with {@link #getFontConfig()} and the time when you call this method,
+     * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by
+     * calling {@link #getFontConfig()} and call this method again with the latest config version.
+     *
+     * @param request A {@link FontFamilyUpdateRequest} to execute.
+     * @param baseVersion A base config version to be updated. You can get the latest config version
+     *                    by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If
+     *                    the system has a newer config version, the update will fail with
+     *                    {@link #RESULT_ERROR_VERSION_MISMATCH}.
+     * @return A result code.
+     * @see FontConfig#getConfigVersion()
+     * @see #getFontConfig()
+     * @see #RESULT_SUCCESS
+     * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE
+     * @see #RESULT_ERROR_VERIFICATION_FAILURE
+     * @see #RESULT_ERROR_VERSION_MISMATCH
+     * @see #RESULT_ERROR_INVALID_FONT_FILE
+     * @see #RESULT_ERROR_INVALID_FONT_NAME
+     * @see #RESULT_ERROR_DOWNGRADING
+     * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
+     * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
+     * @see #RESULT_ERROR_FONT_NOT_FOUND
+     */
+    @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFamily(
+            @NonNull FontFamilyUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+        List<FontUpdateRequest> requests = new ArrayList<>();
+        List<FontFileUpdateRequest> fontFileUpdateRequests = request.getFontFileUpdateRequests();
+        for (int i = 0; i < fontFileUpdateRequests.size(); i++) {
+            FontFileUpdateRequest fontFile = fontFileUpdateRequests.get(i);
+            requests.add(new FontUpdateRequest(fontFile.getParcelFileDescriptor(),
+                    fontFile.getSignature()));
+        }
+        List<FontFamilyUpdateRequest.FontFamily> fontFamilies = request.getFontFamilies();
+        for (int i = 0; i < fontFamilies.size(); i++) {
+            FontFamilyUpdateRequest.FontFamily fontFamily = fontFamilies.get(i);
+            requests.add(new FontUpdateRequest(fontFamily.getName(), fontFamily.getFonts()));
+        }
+        try {
+            return mIFontManager.updateFontFamily(requests, baseVersion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index f551d6a..b79c8f62 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -19,13 +19,17 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.text.FontConfig;
 
+import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents a font update request. Currently only font install request is supported.
@@ -80,6 +84,26 @@
         mFontFamily = fontFamily;
     }
 
+    public FontUpdateRequest(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> variations) {
+        // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily.
+        this(createFontFamily(postScriptName, variations));
+    }
+
+    private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> fonts) {
+        List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size());
+        for (FontFamilyUpdateRequest.Font font : fonts) {
+            // TODO: Support .otf.
+            configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null,
+                    font.getStyle(), 0 /* index */,
+                    FontVariationAxis.toFontVariationSettings(font.getAxes()),
+                    null /* fontFamilyName */));
+        }
+        return new FontConfig.FontFamily(configFonts, postScriptName,
+                LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+    }
+
     protected FontUpdateRequest(Parcel in) {
         mType = in.readInt();
         mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8fe7158..8451ded 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1233,6 +1233,8 @@
                                              int sequenceId) {
             synchronized (mInterfaceLock) {
                 if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
                 }
             }
@@ -1263,7 +1265,12 @@
                     mRequestUpdatedNeeded = false;
                     resumeInternalRepeatingRequest(false);
                 } else if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
+                } else {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                 }
             }
 
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 8bb0b184..10a814a 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1917,9 +1917,18 @@
             (3 << HAL_DATASPACE_TRANSFER_SHIFT) |
             (1 << HAL_DATASPACE_RANGE_SHIFT);
 
-    private static final int HAL_DATASPACE_DEPTH = 0x1000;
-    private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
-    private static final int HAL_DATASPACE_HEIF = 0x1003;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_DEPTH = 0x1000;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_HEIF = 0x1003;
     private static final long DURATION_20FPS_NS = 50000000L;
     /**
      * @see #getDurations(int, int)
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index f3da6a9..886a8c1 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -833,67 +833,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
-        switch (acquireInfo) {
-            case FACE_ACQUIRED_GOOD:
-                return null;
-            case FACE_ACQUIRED_INSUFFICIENT:
-                return context.getString(R.string.face_acquired_insufficient);
-            case FACE_ACQUIRED_TOO_BRIGHT:
-                return context.getString(R.string.face_acquired_too_bright);
-            case FACE_ACQUIRED_TOO_DARK:
-                return context.getString(R.string.face_acquired_too_dark);
-            case FACE_ACQUIRED_TOO_CLOSE:
-                return context.getString(R.string.face_acquired_too_close);
-            case FACE_ACQUIRED_TOO_FAR:
-                return context.getString(R.string.face_acquired_too_far);
-            case FACE_ACQUIRED_TOO_HIGH:
-                return context.getString(R.string.face_acquired_too_high);
-            case FACE_ACQUIRED_TOO_LOW:
-                return context.getString(R.string.face_acquired_too_low);
-            case FACE_ACQUIRED_TOO_RIGHT:
-                return context.getString(R.string.face_acquired_too_right);
-            case FACE_ACQUIRED_TOO_LEFT:
-                return context.getString(R.string.face_acquired_too_left);
-            case FACE_ACQUIRED_POOR_GAZE:
-                return context.getString(R.string.face_acquired_poor_gaze);
-            case FACE_ACQUIRED_NOT_DETECTED:
-                return context.getString(R.string.face_acquired_not_detected);
-            case FACE_ACQUIRED_TOO_MUCH_MOTION:
-                return context.getString(R.string.face_acquired_too_much_motion);
-            case FACE_ACQUIRED_RECALIBRATE:
-                return context.getString(R.string.face_acquired_recalibrate);
-            case FACE_ACQUIRED_TOO_DIFFERENT:
-                return context.getString(R.string.face_acquired_too_different);
-            case FACE_ACQUIRED_TOO_SIMILAR:
-                return context.getString(R.string.face_acquired_too_similar);
-            case FACE_ACQUIRED_PAN_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_pan_too_extreme);
-            case FACE_ACQUIRED_TILT_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_tilt_too_extreme);
-            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_roll_too_extreme);
-            case FACE_ACQUIRED_FACE_OBSCURED:
-                return context.getString(R.string.face_acquired_obscured);
-            case FACE_ACQUIRED_START:
-                return null;
-            case FACE_ACQUIRED_SENSOR_DIRTY:
-                return context.getString(R.string.face_acquired_sensor_dirty);
-            case FACE_ACQUIRED_VENDOR: {
-                String[] msgArray = context.getResources().getStringArray(
-                        R.array.face_acquired_vendor);
-                if (vendorCode < msgArray.length) {
-                    return msgArray[vendorCode];
-                }
-            }
-        }
-        Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
-        return null;
-    }
-
-    /**
      * Used so BiometricPrompt can map the face ones onto existing public constants.
      * @hide
      */
@@ -1387,7 +1326,7 @@
             final int acquireInfo = frame.getData().getAcquiredInfo();
             final int vendorCode = frame.getData().getVendorCode();
             final int helpCode = getHelpCode(acquireInfo, vendorCode);
-            final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+            final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
             mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
 
             // Ensure that only non-null help messages are sent.
@@ -1405,7 +1344,7 @@
             final int acquireInfo = frame.getData().getAcquiredInfo();
             final int vendorCode = frame.getData().getVendorCode();
             final int helpCode = getHelpCode(acquireInfo, vendorCode);
-            final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+            final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
             mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
         }
     }
@@ -1415,4 +1354,124 @@
                 ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
                 : acquireInfo;
     }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static String getAuthHelpMessage(Context context, int acquireInfo, int vendorCode) {
+        switch (acquireInfo) {
+            // No help message is needed for a good capture.
+            case FACE_ACQUIRED_GOOD:
+            case FACE_ACQUIRED_START:
+                return null;
+
+            // Consolidate positional feedback to reduce noise during authentication.
+            case FACE_ACQUIRED_NOT_DETECTED:
+            case FACE_ACQUIRED_TOO_CLOSE:
+            case FACE_ACQUIRED_TOO_FAR:
+            case FACE_ACQUIRED_TOO_HIGH:
+            case FACE_ACQUIRED_TOO_LOW:
+            case FACE_ACQUIRED_TOO_RIGHT:
+            case FACE_ACQUIRED_TOO_LEFT:
+            case FACE_ACQUIRED_POOR_GAZE:
+            case FACE_ACQUIRED_PAN_TOO_EXTREME:
+            case FACE_ACQUIRED_TILT_TOO_EXTREME:
+            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_not_detected);
+
+            // Provide more detailed feedback for other soft errors.
+            case FACE_ACQUIRED_INSUFFICIENT:
+                return context.getString(R.string.face_acquired_insufficient);
+            case FACE_ACQUIRED_TOO_BRIGHT:
+                return context.getString(R.string.face_acquired_too_bright);
+            case FACE_ACQUIRED_TOO_DARK:
+                return context.getString(R.string.face_acquired_too_dark);
+            case FACE_ACQUIRED_TOO_MUCH_MOTION:
+                return context.getString(R.string.face_acquired_too_much_motion);
+            case FACE_ACQUIRED_RECALIBRATE:
+                return context.getString(R.string.face_acquired_recalibrate);
+            case FACE_ACQUIRED_TOO_DIFFERENT:
+                return context.getString(R.string.face_acquired_too_different);
+            case FACE_ACQUIRED_TOO_SIMILAR:
+                return context.getString(R.string.face_acquired_too_similar);
+            case FACE_ACQUIRED_FACE_OBSCURED:
+                return context.getString(R.string.face_acquired_obscured);
+            case FACE_ACQUIRED_SENSOR_DIRTY:
+                return context.getString(R.string.face_acquired_sensor_dirty);
+
+            // Find and return the appropriate vendor-specific message.
+            case FACE_ACQUIRED_VENDOR: {
+                String[] msgArray = context.getResources().getStringArray(
+                        R.array.face_acquired_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+
+        Slog.w(TAG, "Unknown authentication acquired message: " + acquireInfo + ", " + vendorCode);
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static String getEnrollHelpMessage(Context context, int acquireInfo, int vendorCode) {
+        switch (acquireInfo) {
+            case FACE_ACQUIRED_GOOD:
+            case FACE_ACQUIRED_START:
+                return null;
+            case FACE_ACQUIRED_INSUFFICIENT:
+                return context.getString(R.string.face_acquired_insufficient);
+            case FACE_ACQUIRED_TOO_BRIGHT:
+                return context.getString(R.string.face_acquired_too_bright);
+            case FACE_ACQUIRED_TOO_DARK:
+                return context.getString(R.string.face_acquired_too_dark);
+            case FACE_ACQUIRED_TOO_CLOSE:
+                return context.getString(R.string.face_acquired_too_close);
+            case FACE_ACQUIRED_TOO_FAR:
+                return context.getString(R.string.face_acquired_too_far);
+            case FACE_ACQUIRED_TOO_HIGH:
+                return context.getString(R.string.face_acquired_too_high);
+            case FACE_ACQUIRED_TOO_LOW:
+                return context.getString(R.string.face_acquired_too_low);
+            case FACE_ACQUIRED_TOO_RIGHT:
+                return context.getString(R.string.face_acquired_too_right);
+            case FACE_ACQUIRED_TOO_LEFT:
+                return context.getString(R.string.face_acquired_too_left);
+            case FACE_ACQUIRED_POOR_GAZE:
+                return context.getString(R.string.face_acquired_poor_gaze);
+            case FACE_ACQUIRED_NOT_DETECTED:
+                return context.getString(R.string.face_acquired_not_detected);
+            case FACE_ACQUIRED_TOO_MUCH_MOTION:
+                return context.getString(R.string.face_acquired_too_much_motion);
+            case FACE_ACQUIRED_RECALIBRATE:
+                return context.getString(R.string.face_acquired_recalibrate);
+            case FACE_ACQUIRED_TOO_DIFFERENT:
+                return context.getString(R.string.face_acquired_too_different);
+            case FACE_ACQUIRED_TOO_SIMILAR:
+                return context.getString(R.string.face_acquired_too_similar);
+            case FACE_ACQUIRED_PAN_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_pan_too_extreme);
+            case FACE_ACQUIRED_TILT_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_tilt_too_extreme);
+            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_roll_too_extreme);
+            case FACE_ACQUIRED_FACE_OBSCURED:
+                return context.getString(R.string.face_acquired_obscured);
+            case FACE_ACQUIRED_SENSOR_DIRTY:
+                return context.getString(R.string.face_acquired_sensor_dirty);
+            case FACE_ACQUIRED_VENDOR: {
+                String[] msgArray = context.getResources().getStringArray(
+                        R.array.face_acquired_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+        Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
+        return null;
+    }
 }
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 383321b..bd40409 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -4,3 +4,6 @@
 wyattriley@google.com
 etn@google.com
 weiwa@google.com
+
+# ContextHub team
+per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d6774d4..933256a 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -58,6 +58,9 @@
             in LinkAddress localAddr,
             in String callingPackage);
 
+    void setNetworkForTunnelInterface(
+            int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
     void deleteTunnelInterface(int resourceId, in String callingPackage);
 
     IpSecTransformResponse createTransform(
diff --git a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
new file mode 100644
index 0000000..7979afc
--- /dev/null
+++ b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
@@ -0,0 +1,23 @@
+/**
+ *
+ * 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;
+
+/** @hide */
+oneway interface IOnSetOemNetworkPreferenceListener {
+    void onComplete();
+}
diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl
new file mode 100644
index 0000000..271efe4
--- /dev/null
+++ b/core/java/android/net/IVpnManager.aidl
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.Network;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+
+/**
+ * Interface that manages VPNs.
+ */
+/** {@hide} */
+interface IVpnManager {
+    /** VpnService APIs */
+    boolean prepareVpn(String oldPackage, String newPackage, int userId);
+    void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
+    ParcelFileDescriptor establishVpn(in VpnConfig config);
+    boolean addVpnAddress(String address, int prefixLength);
+    boolean removeVpnAddress(String address, int prefixLength);
+    boolean setUnderlyingNetworksForVpn(in Network[] networks);
+
+    /** VpnManager APIs */
+    boolean provisionVpnProfile(in VpnProfile profile, String packageName);
+    void deleteVpnProfile(String packageName);
+    void startVpnProfile(String packageName);
+    void stopVpnProfile(String packageName);
+
+    /** Always-on VPN APIs */
+    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
+    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+            in List<String> lockdownAllowlist);
+    String getAlwaysOnVpnPackage(int userId);
+    boolean isVpnLockdownEnabled(int userId);
+    List<String> getVpnLockdownAllowlist(int userId);
+    boolean isCallerCurrentAlwaysOnVpnApp();
+    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
+
+    /** Legacy VPN APIs */
+    void startLegacyVpn(in VpnProfile profile);
+    LegacyVpnInfo getLegacyVpnInfo(int userId);
+    boolean updateLockdownVpn();
+
+    /** General system APIs */
+    VpnConfig getVpnConfig(int userId);
+    void factoryReset();
+}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 70bca30..98acd98 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -782,6 +782,42 @@
             }
         }
 
+        /**
+         * Update the underlying network for this IpSecTunnelInterface.
+         *
+         * <p>This new underlying network will be used for all transforms applied AFTER this call is
+         * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+         * this tunnel interface, traffic will still use the old SA, and be routed on the old
+         * underlying network.
+         *
+         * <p>To migrate IPsec tunnel mode traffic, a caller should:
+         *
+         * <ol>
+         *   <li>Update the IpSecTunnelInterface’s underlying network.
+         *   <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+         *       IpSecTunnelInterface.
+         * </ol>
+         *
+         * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+         *     This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
+         *     this method will throw an {@link IllegalArgumentException}.
+         */
+        // TODO: b/169171001 Update the documentation when transform migration is supported.
+        // The purpose of making updating network and applying transforms separate is to leave open
+        // the possibility to support lossless migration procedures. To do that, Android platform
+        // will need to support multiple inbound tunnel mode transforms, just like it can support
+        // multiple transport mode transforms.
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+        public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+            try {
+                mService.setNetworkForTunnelInterface(
+                        mResourceId, underlyingNetwork, mOpPackageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
                 @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
                 @NonNull Network underlyingNetwork)
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3e6237d..6353a25 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -55,6 +56,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.NETWORK_POLICY_SERVICE)
 public class NetworkPolicyManager {
 
@@ -125,6 +127,7 @@
     public static final int RULE_REJECT_ALL = 1 << 6;
     /**
      * Reject traffic on all networks for restricted networking mode.
+     * @hide
      */
     public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10;
 
@@ -351,6 +354,7 @@
     }
 
     /** @hide */
+    @TestApi
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void setRestrictBackground(boolean restrictBackground) {
         try {
@@ -361,6 +365,7 @@
     }
 
     /** @hide */
+    @TestApi
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean getRestrictBackground() {
         try {
@@ -506,6 +511,8 @@
 
     /**
      * Get multipath preference for the given network.
+     *
+     * @hide
      */
     public int getMultipathPreference(Network network) {
         try {
@@ -624,7 +631,9 @@
     }
 
     /** @hide */
-    public static String resolveNetworkId(WifiConfiguration config) {
+    @TestApi
+    @NonNull
+    public static String resolveNetworkId(@NonNull WifiConfiguration config) {
         return WifiInfo.sanitizeSsid(config.isPasspoint()
                 ? config.providerFriendlyName : config.SSID);
     }
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index 5e56164..b403455 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcelable;
 
@@ -29,11 +30,12 @@
 import java.util.Objects;
 
 /** @hide */
+@SystemApi
 public final class OemNetworkPreferences implements Parcelable {
     /**
-     * Use default behavior requesting networks. Equivalent to not setting any preference at all.
+     * Default in case this value is not set. Using it will result in an error.
      */
-    public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0;
+    public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0;
 
     /**
      * If an unmetered network is available, use it.
@@ -45,17 +47,17 @@
     /**
      * If an unmetered network is available, use it.
      * Otherwise, if a network with the OEM_PAID capability is available, use it.
-     * Otherwise, the app doesn't get a network.
+     * Otherwise, the app doesn't get a default network.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PAID networks.
+     * Use only NET_CAPABILITY_OEM_PAID networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PRIVATE networks.
+     * Use only NET_CAPABILITY_OEM_PRIVATE networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4;
 
@@ -95,8 +97,6 @@
     /**
      * Builder used to create {@link OemNetworkPreferences} objects.  Specify the preferred Network
      * to package name mappings.
-     *
-     * @hide
      */
     public static final class Builder {
         private final Bundle mNetworkMappings;
@@ -135,7 +135,7 @@
          * @return The builder to facilitate chaining.
          */
         @NonNull
-        public Builder removeNetworkPreference(@NonNull final String packageName) {
+        public Builder clearNetworkPreference(@NonNull final String packageName) {
             Objects.requireNonNull(packageName);
             mNetworkMappings.remove(packageName);
             return this;
@@ -160,7 +160,7 @@
 
     /** @hide */
     @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = {
-            OEM_NETWORK_PREFERENCE_DEFAULT,
+            OEM_NETWORK_PREFERENCE_UNINITIALIZED,
             OEM_NETWORK_PREFERENCE_OEM_PAID,
             OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK,
             OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY,
@@ -174,12 +174,14 @@
      *
      * @param value int value of OemNetworkPreference
      * @return string version of OemNetworkPreference
+     *
+     * @hide
      */
     @NonNull
     public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) {
         switch (value) {
-            case OEM_NETWORK_PREFERENCE_DEFAULT:
-                return "OEM_NETWORK_PREFERENCE_DEFAULT";
+            case OEM_NETWORK_PREFERENCE_UNINITIALIZED:
+                return "OEM_NETWORK_PREFERENCE_UNINITIALIZED";
             case OEM_NETWORK_PREFERENCE_OEM_PAID:
                 return "OEM_NETWORK_PREFERENCE_OEM_PAID";
             case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
similarity index 67%
rename from packages/Connectivity/framework/src/android/net/VpnManager.java
rename to core/java/android/net/VpnManager.java
index 1e30283..f472ed4 100644
--- a/packages/Connectivity/framework/src/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -37,6 +38,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.GeneralSecurityException;
+import java.util.List;
 
 /**
  * This class provides an interface for apps to manage platform VPN profiles
@@ -76,13 +78,19 @@
     @Deprecated
     public static final int TYPE_VPN_LEGACY = 3;
 
+    /**
+     * Channel for VPN notifications.
+     * @hide
+     */
+    public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
+
     /** @hide */
     @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
     @Retention(RetentionPolicy.SOURCE)
     public @interface VpnType {}
 
     @NonNull private final Context mContext;
-    @NonNull private final IConnectivityManager mService;
+    @NonNull private final IVpnManager mService;
 
     private static Intent getIntentForConfirmation() {
         final Intent intent = new Intent();
@@ -101,9 +109,9 @@
      *
      * @hide
      */
-    public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
+    public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) {
         mContext = checkNotNull(ctx, "missing Context");
-        mService = checkNotNull(service, "missing IConnectivityManager");
+        mService = checkNotNull(service, "missing IVpnManager");
     }
 
     /**
@@ -195,6 +203,19 @@
     }
 
     /**
+     * Resets all VPN settings back to factory defaults.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void factoryReset() {
+        try {
+            mService.factoryReset();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Prepare for a VPN application.
      * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
      * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
@@ -240,6 +261,108 @@
     }
 
     /**
+     * Checks if a VPN app supports always-on mode.
+     *
+     * In order to support the always-on feature, an app has to
+     * <ul>
+     *     <li>target {@link VERSION_CODES#N API 24} or above, and
+     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+     *         meta-data field.
+     * </ul>
+     *
+     * @param userId The identifier of the user for whom the VPN app is installed.
+     * @param vpnPackage The canonical package name of the VPN app.
+     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+     * @hide
+     */
+    public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+        try {
+            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param userId The identifier of the user to set an always-on VPN for.
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+     *        {@code false} otherwise.
+     * @param lockdownAllowlist The list of packages that are allowed to access network directly
+     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+     *         this method must be called when a package that should be allowed is installed or
+     *         uninstalled.
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
+            boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
+        try {
+            return mService.setAlwaysOnVpnPackage(
+                    userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the package name of the currently set always-on VPN application.
+     * If there is no always-on VPN set, or the VPN is provided by the system instead
+     * of by an app, {@code null} will be returned.
+     *
+     * @return Package name of VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public String getAlwaysOnVpnPackageForUser(int userId) {
+        try {
+            return mService.getAlwaysOnVpnPackage(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return whether always-on VPN is in lockdown mode.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean isVpnLockdownEnabled(int userId) {
+        try {
+            return mService.isVpnLockdownEnabled(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return the list of packages that are allowed to access network when always-on VPN is in
+     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        try {
+            return mService.getVpnLockdownAllowlist(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the legacy VPN information for the specified user ID.
      * @hide
      */
diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java
similarity index 98%
rename from packages/Connectivity/framework/src/android/net/VpnService.java
rename to core/java/android/net/VpnService.java
index 8e90a119..e43b0b6 100644
--- a/packages/Connectivity/framework/src/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -170,12 +170,11 @@
             "android.net.VpnService.SUPPORTS_ALWAYS_ON";
 
     /**
-     * Use IConnectivityManager since those methods are hidden and not
-     * available in ConnectivityManager.
+     * Use IVpnManager since those methods are hidden and not available in VpnManager.
      */
-    private static IConnectivityManager getService() {
-        return IConnectivityManager.Stub.asInterface(
-                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+    private static IVpnManager getService() {
+        return IVpnManager.Stub.asInterface(
+                ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE));
     }
 
     /**
@@ -226,15 +225,15 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.CONTROL_VPN)
     public static void prepareAndAuthorize(Context context) {
-        IConnectivityManager cm = getService();
+        IVpnManager vm = getService();
         String packageName = context.getPackageName();
         try {
             // Only prepare if we're not already prepared.
             int userId = context.getUserId();
-            if (!cm.prepareVpn(packageName, null, userId)) {
-                cm.prepareVpn(null, packageName, userId);
+            if (!vm.prepareVpn(packageName, null, userId)) {
+                vm.prepareVpn(null, packageName, userId);
             }
-            cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
+            vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
         } catch (RemoteException e) {
             // ignore
         }
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 72a6e16..bf229e0 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -47,7 +47,9 @@
             POWER_COMPONENT_SYSTEM_SERVICES,
             POWER_COMPONENT_SENSORS,
             POWER_COMPONENT_GNSS,
+            POWER_COMPONENT_WAKELOCK,
             POWER_COMPONENT_SCREEN,
+            POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface PowerComponent {
@@ -64,9 +66,14 @@
     public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
     public static final int POWER_COMPONENT_SENSORS = 9;
     public static final int POWER_COMPONENT_GNSS = 10;
+    public static final int POWER_COMPONENT_WAKELOCK = 12;
     public static final int POWER_COMPONENT_SCREEN = 13;
+    // Power that is re-attributed to other battery consumers. For example, for System Server
+    // this represents the power attributed to apps requesting system services.
+    // The value should be negative or zero.
+    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 14;
 
-    public static final int POWER_COMPONENT_COUNT = 14;
+    public static final int POWER_COMPONENT_COUNT = 15;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -87,6 +94,7 @@
             TIME_COMPONENT_MOBILE_RADIO,
             TIME_COMPONENT_SENSORS,
             TIME_COMPONENT_GNSS,
+            TIME_COMPONENT_WAKELOCK,
             TIME_COMPONENT_SCREEN,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -104,6 +112,7 @@
     public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
     public static final int TIME_COMPONENT_SENSORS = 9;
     public static final int TIME_COMPONENT_GNSS = 10;
+    public static final int TIME_COMPONENT_WAKELOCK = 12;
     public static final int TIME_COMPONENT_SCREEN = 13;
 
     public static final int TIME_COMPONENT_COUNT = 14;
@@ -236,5 +245,13 @@
                     componentUsageTimeMillis);
             return (T) this;
         }
+
+        /**
+         * Returns the total power accumulated by this builder so far. It may change
+         * by the time the {@code build()} method is called.
+         */
+        public double getTotalPower() {
+            return mPowerComponentsBuilder.getTotalPower();
+        }
     }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d631f68..01a8901 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -5296,8 +5296,11 @@
                     }
                     if (bs.customMeasuredPowerMah != null) {
                         for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) {
-                            pw.print(" custom[" + idx + "]=");
-                            printmAh(pw, bs.customMeasuredPowerMah[idx]);
+                            final double customPowerMah = bs.customMeasuredPowerMah[idx];
+                            if (customPowerMah != 0) {
+                                pw.print(" custom[" + idx + "]=");
+                                printmAh(pw, customPowerMah);
+                            }
                         }
                     }
                     pw.print(" )");
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index eac03dc..e3b13f4 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2566,6 +2566,14 @@
     public static native long getDmabufTotalExportedKb();
 
     /**
+     * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF
+     * heaps frameworks or -1 in the case of an error.
+     *
+     * @hide
+     */
+    public static native long getDmabufHeapTotalExportedKb();
+
+    /**
      * Return memory size in kilobytes allocated for ION heaps or -1 if
      * /sys/kernel/ion/total_heaps_kb could not be read.
      *
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 52f0ce1..4d160da 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -35,4 +35,9 @@
      * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries
      */
     Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
+
+    /**
+     * @see SystemConfigManager#getSystemPermissionUids
+     */
+    int[] getSystemPermissionUids(String permissionName);
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index dac1ede..a04047d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,4 +1,6 @@
 # Haptics
+per-file CombinedVibrationEffect.aidl = michaelwr@google.com
+per-file CombinedVibrationEffect.java = michaelwr@google.com
 per-file ExternalVibration.aidl = michaelwr@google.com
 per-file ExternalVibration.java = michaelwr@google.com
 per-file IExternalVibrationController.aidl = michaelwr@google.com
@@ -6,9 +8,11 @@
 per-file IVibratorManagerService.aidl = michaelwr@google.com
 per-file NullVibrator.java = michaelwr@google.com
 per-file SystemVibrator.java = michaelwr@google.com
+per-file SystemVibratorManager.java = michaelwr@google.com
 per-file VibrationEffect.aidl = michaelwr@google.com
 per-file VibrationEffect.java = michaelwr@google.com
 per-file Vibrator.java = michaelwr@google.com
+per-file VibratorManager.java = michaelwr@google.com
 
 # PowerManager
 per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 1337d55..ac23285 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -38,11 +38,7 @@
         mCustomPowerComponentCount = builder.mCustomPowerComponentCount;
         mPowerComponents = builder.mPowerComponents;
         mTimeComponents = builder.mTimeComponents;
-        double totalPower = 0;
-        for (int i = mPowerComponents.length - 1; i >= 0; i--) {
-            totalPower += mPowerComponents[i];
-        }
-        mTotalPowerConsumed = totalPower;
+        mTotalPowerConsumed = builder.getTotalPower();
     }
 
     PowerComponents(@NonNull Parcel source) {
@@ -264,6 +260,18 @@
         }
 
         /**
+         * Returns the total power accumulated by this builder so far. It may change
+         * by the time the {@code build()} method is called.
+         */
+        public double getTotalPower() {
+            double totalPower = 0;
+            for (int i = mPowerComponents.length - 1; i >= 0; i--) {
+                totalPower += mPowerComponents[i];
+            }
+            return totalPower;
+        }
+
+        /**
          * Creates a read-only object out of the Builder values.
          */
         @NonNull
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 8068c87..54d2df8 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -970,6 +970,16 @@
             throws IllegalArgumentException, SecurityException;
 
     /**
+     *
+     * Create a new process group in the cgroup uid/pid hierarchy
+     *
+     * @return <0 in case of error
+     *
+     * @hide
+     */
+    public static final native int createProcessGroup(int uid, int pid);
+
+    /**
      * On some devices, the foreground process may have one or more CPU
      * cores exclusively reserved for it. This method can be used to
      * retrieve which cores that are (if any), so the calling process
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 3f0632b..9bfa8ad 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -111,4 +111,22 @@
             return Collections.emptyMap();
         }
     }
+
+    /**
+     * Get uids which have been granted given permission in system configuration.
+     *
+     * The uids and assigning permissions are defined on data/etc/platform.xml
+     *
+     * @param permissionName The target permission.
+     * @return The uids have been granted given permission in system configuration.
+     */
+    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    @NonNull
+    public int[] getSystemPermissionUids(@NonNull String permissionName) {
+        try {
+            return mInterface.getSystemPermissionUids(permissionName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef81ed7..f02e532 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10780,6 +10780,13 @@
                 "location_ignore_settings_package_whitelist";
 
         /**
+         * Whether to throttle location when the device is in doze and still.
+         * @hide
+         */
+        public static final String LOCATION_ENABLE_STATIONARY_THROTTLE =
+                "location_enable_stationary_throttle";
+
+        /**
         * Whether TV will switch to MHL port when a mobile device is plugged in.
         * (0 = false, 1 = true)
         * @hide
diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java
index 053fb2b..9de75ca 100644
--- a/core/java/android/service/notification/NotificationListenerFilter.java
+++ b/core/java/android/service/notification/NotificationListenerFilter.java
@@ -32,6 +32,7 @@
  */
 public class NotificationListenerFilter implements Parcelable {
     private int mAllowedNotificationTypes;
+    // VersionedPackage is holding the pkg name and pkg uid
     private ArraySet<VersionedPackage> mDisallowedPackages;
 
     public NotificationListenerFilter() {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 64cddc3..f66f85b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -80,6 +80,10 @@
  *     &lt;intent-filter>
  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
  *     &lt;/intent-filter>
+ *     &lt;meta-data
+ *               android:name="android.service.notification.default_filter_types"
+ *               android:value="1,2">
+ *           &lt;/meta-data>
  * &lt;/service></pre>
  *
  * <p>The service should wait for the {@link #onListenerConnected()} event
@@ -103,6 +107,21 @@
     private final String TAG = getClass().getSimpleName();
 
     /**
+     * The name of the {@code meta-data} tag containing a comma separated list of default
+     * integer notification types that should be provided to this listener. See
+     * {@link #FLAG_FILTER_TYPE_ONGOING},
+     * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
+     * and {@link #FLAG_FILTER_TYPE_SILENT}.
+     * <p>This value will only be read if the app has not previously specified a default type list,
+     * and if the user has not overridden the allowed types.</p>
+     * <p>An absent value means 'allow all types'.
+     * A present but empty value means 'allow no types'.</p>
+     *
+     */
+    public static final String META_DATA_DEFAULT_FILTER_TYPES
+            = "android.service.notification.default_filter_types";
+
+    /**
      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
      *     Normal interruption filter.
      */
@@ -254,23 +273,19 @@
     /**
      * A flag value indicating that this notification listener can see conversation type
      * notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
     /**
      * A flag value indicating that this notification listener can see altering type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ALERTING = 2;
     /**
      * A flag value indicating that this notification listener can see silent type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_SILENT = 4;
     /**
      * A flag value indicating that this notification listener can see important
      * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ONGOING = 8;
 
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index a750b68..87add57 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -95,6 +95,21 @@
     public static final String EXTRA_ERROR =
             "android.service.storage.extra.error";
 
+    /**
+     * {@link Bundle} key for a package name {@link String} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name";
+
+    /**
+     * {@link Bundle} key for a {@link Long} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_ANR_TIMEOUT_MS =
+            "android.service.storage.extra.anr_timeout_ms";
+
     /** @hide */
     @IntDef(flag = true, prefix = {"FLAG_SESSION_"},
         value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
@@ -162,6 +177,15 @@
         throw new UnsupportedOperationException("onFreeCacheRequested not implemented");
     }
 
+    /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long onGetAnrDelayMillis(@NonNull String packageName, int uid) {
+        throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented");
+    }
+
     @Override
     @NonNull
     public final IBinder onBind(@NonNull Intent intent) {
@@ -222,6 +246,19 @@
             });
         }
 
+        @Override
+        public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback)
+                throws RemoteException {
+            mHandler.post(() -> {
+                try {
+                    long timeoutMs = onGetAnrDelayMillis(packageName, uid);
+                    sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback);
+                } catch (Throwable t) {
+                    sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback);
+                }
+            });
+        }
+
         private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
             Bundle bundle = new Bundle();
             bundle.putString(EXTRA_SESSION_ID, sessionId);
@@ -230,5 +267,16 @@
             }
             callback.sendResult(bundle);
         }
+
+        private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable,
+                RemoteCallback callback) {
+            Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+            bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs);
+            if (throwable != null) {
+                bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+            }
+            callback.sendResult(bundle);
+        }
     }
 }
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index d06671b..2e0bd86 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,4 +32,5 @@
         in RemoteCallback callback);
     void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
         in RemoteCallback callback);
+    void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6b13a29..f1f6786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1375,6 +1375,7 @@
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
                 mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                         attrs.getTitle().toString());
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
                 updateColorModeIfNeeded(attrs.getColorMode());
                 updateForceDarkMode();
                 if (mAttachInfo.mThreadedRenderer != null) {
@@ -1941,6 +1942,10 @@
             mBlastBufferQueue.destroy();
             mBlastBufferQueue = null;
         }
+
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+        }
     }
 
     /**
@@ -2836,8 +2841,7 @@
                         mScroller.abortAnimation();
                     }
                     // Our surface is gone
-                    if (mAttachInfo.mThreadedRenderer != null &&
-                            mAttachInfo.mThreadedRenderer.isEnabled()) {
+                    if (isHardwareEnabled()) {
                         mAttachInfo.mThreadedRenderer.destroy();
                     }
                 } else if ((surfaceReplaced
@@ -3923,8 +3927,15 @@
         };
     }
 
+    /**
+     * @hide
+     */
+    public boolean isHardwareEnabled() {
+        return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
+    }
+
     private boolean addFrameCompleteCallbackIfNeeded() {
-        if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) {
+        if (!isHardwareEnabled()) {
             return false;
         }
 
@@ -4268,7 +4279,7 @@
 
         boolean useAsyncReport = false;
         if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
-            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
+            if (isHardwareEnabled()) {
                 // If accessibility focus moved, always invalidate the root.
                 boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
                 mInvalidateRootRequested = false;
@@ -7645,6 +7656,9 @@
                     mSurface.transferFrom(blastSurface);
                 }
             }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+            }
         } else {
             destroySurface();
         }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af18293..4ecdd78 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1703,6 +1703,30 @@
     public abstract void setBackgroundDrawable(Drawable drawable);
 
     /**
+     * Blurs the screen behind the window within the bounds of the window.
+     *
+     * The density of the blur is set by the blur radius. The radius defines the size
+     * of the neighbouring area, from which pixels will be averaged to form the final
+     * color for each pixel. The operation approximates a Gaussian blur.
+     * A radius of 0 means no blur. The higher the radius, the denser the blur.
+     *
+     * The window background drawable is drawn on top of the blurred region. The blur
+     * region bounds and rounded corners will mimic those of the background drawable.
+     *
+     * For the blur region to be visible, the window has to be translucent. See
+     * {@link android.R.styleable#Window_windowIsTranslucent}.
+     *
+     * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius},
+     * which blurs the whole screen behind the window. Background blur blurs the screen behind
+     * only within the bounds of the window.
+     *
+     * @param blurRadius The blur radius to use for window background blur in pixels
+     *
+     * @see android.R.styleable#Window_windowBackgroundBlurRadius
+     */
+    public void setBackgroundBlurRadius(int blurRadius) {}
+
+    /**
      * Set the value for a drawable feature of this window, from a resource
      * identifier.  You must have called requestFeature(featureId) before
      * calling this function.
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 681fd14..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -25,8 +25,12 @@
 import android.content.res.TypedArray;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.animation.AnimationUtils;
@@ -149,6 +153,8 @@
     private float mDisplacement = 0.5f;
     private float mTargetDisplacement = 0.5f;
     private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
+    private Matrix mTmpMatrix = null;
+    private float[] mTmpPoints = null;
 
     /**
      * Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -250,11 +256,18 @@
     public void onPull(float deltaDistance, float displacement) {
         final long now = AnimationUtils.currentAnimationTimeMillis();
         mTargetDisplacement = displacement;
-        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration
+                && mEdgeEffectType == TYPE_GLOW) {
             return;
         }
         if (mState != STATE_PULL) {
-            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+            if (mEdgeEffectType == TYPE_STRETCH) {
+                // Restore the mPullDistance to the fraction it is currently showing -- we want
+                // to "catch" the current stretch value.
+                mPullDistance = mDistance;
+            } else {
+                mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+            }
         }
         mState = STATE_PULL;
 
@@ -313,6 +326,15 @@
     public float onPullDistance(float deltaDistance, float displacement) {
         float finalDistance = Math.max(0f, deltaDistance + mDistance);
         float delta = finalDistance - mDistance;
+        if (delta == 0f && mDistance == 0f) {
+            return 0f; // No pull, don't do anything.
+        }
+
+        if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) {
+            // Catch the edge glow in the middle of an animation.
+            mPullDistance = mDistance;
+            mState = STATE_PULL;
+        }
         onPull(delta, displacement);
         return delta;
     }
@@ -466,33 +488,59 @@
      * Draw into the provided canvas. Assumes that the canvas has been rotated
      * accordingly and the size has been set. The effect will be drawn the full
      * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
-     * 1.f of height.
+     * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a
+     * hardware canvas, e.g. {@link RenderNode#beginRecording()}.
      *
      * @param canvas Canvas to draw into
      * @return true if drawing should continue beyond this frame to continue the
      *         animation
      */
     public boolean draw(Canvas canvas) {
-        update();
+        if (mEdgeEffectType == TYPE_GLOW) {
+            update();
+            final int count = canvas.save();
 
-        final int count = canvas.save();
+            final float centerX = mBounds.centerX();
+            final float centerY = mBounds.height() - mRadius;
 
-        final float centerX = mBounds.centerX();
-        final float centerY = mBounds.height() - mRadius;
+            canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
 
-        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
+            final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+            float translateX = mBounds.width() * displacement / 2;
 
-        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
-        float translateX = mBounds.width() * displacement / 2;
+            canvas.clipRect(mBounds);
+            canvas.translate(translateX, 0);
+            mPaint.setAlpha((int) (0xff * mGlowAlpha));
+            canvas.drawCircle(centerX, centerY, mRadius, mPaint);
+            canvas.restoreToCount(count);
+        } else if (canvas instanceof RecordingCanvas) {
+            if (mState != STATE_PULL) {
+                update();
+            }
+            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+            if (mTmpMatrix == null) {
+                mTmpMatrix = new Matrix();
+                mTmpPoints = new float[4];
+            }
+            //noinspection deprecation
+            recordingCanvas.getMatrix(mTmpMatrix);
+            mTmpPoints[0] = mBounds.width() * mDisplacement;
+            mTmpPoints[1] = mDistance * mBounds.height();
+            mTmpPoints[2] = mTmpPoints[0];
+            mTmpPoints[3] = 0;
+            mTmpMatrix.mapPoints(mTmpPoints);
+            float x = mTmpPoints[0] - mTmpPoints[2];
+            float y = mTmpPoints[1] - mTmpPoints[3];
 
-        canvas.clipRect(mBounds);
-        canvas.translate(translateX, 0);
-        mPaint.setAlpha((int) (0xff * mGlowAlpha));
-        canvas.drawCircle(centerX, centerY, mRadius, mPaint);
-        canvas.restoreToCount(count);
+            RenderNode renderNode = recordingCanvas.mNode;
+
+            // TODO: use stretchy RenderEffect and use internal API when it is ready
+            // TODO: wrap existing RenderEffect
+            renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y));
+        }
 
         boolean oneLastFrame = false;
-        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
+        if (mState == STATE_RECEDE && mDistance == 0) {
             mState = STATE_IDLE;
             oneLastFrame = true;
         }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe37c53..0f2089a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12940,17 +12940,11 @@
             return false;
         }
 
-        final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
-        final ClipDescription description = clipData.getDescription();
+        final ClipDescription description =
+                getClipboardManagerForUser().getPrimaryClipDescription();
         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
-        final CharSequence text = clipData.getItemAt(0).getText();
-        if (isPlainType && (text instanceof Spanned)) {
-            Spanned spanned = (Spanned) text;
-            if (TextUtils.hasStyleSpan(spanned)) {
-                return true;
-            }
-        }
-        return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
+        return (isPlainType && description.isStyledText())
+                || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
     }
 
     boolean canProcessText() {
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 88b2257..8f541d0 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -42,6 +42,11 @@
     void removeStartingWindow(int taskId);
 
     /**
+     * Called when the Task want to copy the splash screen.
+     */
+    void copySplashScreenView(int taskId);
+
+    /**
      * A callback when the Task is available for the registered organizer. The client is responsible
      * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
      * be hidden so it is up to the organizer to show this task.
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
new file mode 100644
index 0000000..4b88a9b
--- /dev/null
+++ b/core/java/android/window/SplashScreen.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Singleton;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * The interface that apps use to talk to the splash screen.
+ * <p>
+ * Each splash screen instance is bound to a particular {@link Activity}.
+ * To obtain a {@link SplashScreen} for an Activity, use
+ * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p>
+ */
+public interface SplashScreen {
+    /**
+     * <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its
+     * own. Normally the splash screen will show on screen before the content of the activity has
+     * been drawn, and disappear when the activity is showing on the screen. With this listener set,
+     * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if
+     * splash screen is showed, then the activity can create its own exit animation based on the
+     * SplashScreenView.</p>
+     *
+     * <p> Note that this method must be called before splash screen leave, so it only takes effect
+     * during or before {@link Activity#onResume}.</p>
+     *
+     * @param listener the listener for receive the splash screen with
+     *
+     * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView)
+     */
+    @SuppressLint("ExecutorRegistration")
+    void setOnExitAnimationListener(@Nullable SplashScreen.OnExitAnimationListener listener);
+
+    /**
+     * Listens for the splash screen exit event.
+     */
+    interface OnExitAnimationListener {
+        /**
+         * When receiving this callback, the {@link SplashScreenView} object will be drawing on top
+         * of the activity. The {@link SplashScreenView} represents the splash screen view
+         * object, developer can make an exit animation based on this view.</p>
+         *
+         * <p>If {@link SplashScreenView#remove} is not called after 5000ms, the method will be
+         * automatically called and the splash screen removed.</p>
+         *
+         * <p>This method is never invoked if your activity sets
+         * {@link #setOnExitAnimationListener} to <code>null</code>..
+         *
+         * @param view The view object which on top of this Activity.
+         * @see #setOnExitAnimationListener
+         */
+        void onSplashScreenExit(@NonNull SplashScreenView view);
+    }
+
+    /**
+     * @hide
+     */
+    class SplashScreenImpl implements SplashScreen {
+        private OnExitAnimationListener mExitAnimationListener;
+        private final IBinder mActivityToken;
+        private final SplashScreenManagerGlobal mGlobal;
+
+        public SplashScreenImpl(Context context) {
+            mActivityToken = context.getActivityToken();
+            mGlobal = SplashScreenManagerGlobal.getInstance();
+        }
+
+        @Override
+        public void setOnExitAnimationListener(
+                @Nullable SplashScreen.OnExitAnimationListener listener) {
+            if (mActivityToken == null) {
+                // This is not an activity.
+                return;
+            }
+            synchronized (mGlobal.mGlobalLock) {
+                mExitAnimationListener = listener;
+                if (listener != null) {
+                    mGlobal.addImpl(this);
+                } else {
+                    mGlobal.removeImpl(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * This class is only used internally to manage the activities for this process.
+     *
+     * @hide
+     */
+    class SplashScreenManagerGlobal {
+        private static final String TAG = SplashScreen.class.getSimpleName();
+        private final Object mGlobalLock = new Object();
+        private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();
+
+        private SplashScreenManagerGlobal() {
+            ActivityThread.currentActivityThread().registerSplashScreenManager(this);
+        }
+
+        public static SplashScreenManagerGlobal getInstance() {
+            return sInstance.get();
+        }
+
+        private static final Singleton<SplashScreenManagerGlobal> sInstance =
+                new Singleton<SplashScreenManagerGlobal>() {
+                    @Override
+                    protected SplashScreenManagerGlobal create() {
+                        return new SplashScreenManagerGlobal();
+                    }
+                };
+
+        private void addImpl(SplashScreenImpl impl) {
+            synchronized (mGlobalLock) {
+                mImpls.add(impl);
+            }
+        }
+
+        private void removeImpl(SplashScreenImpl impl) {
+            synchronized (mGlobalLock) {
+                mImpls.remove(impl);
+            }
+        }
+
+        private SplashScreenImpl findImpl(IBinder token) {
+            synchronized (mGlobalLock) {
+                for (SplashScreenImpl impl : mImpls) {
+                    if (impl.mActivityToken == token) {
+                        return impl;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public void tokenDestroyed(IBinder token) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                if (impl != null) {
+                    removeImpl(impl);
+                }
+            }
+        }
+
+        public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                if (impl == null) {
+                    return;
+                }
+                if (impl.mExitAnimationListener == null) {
+                    Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token);
+                    return;
+                }
+                impl.mExitAnimationListener.onSplashScreenExit(view);
+            }
+        }
+
+        public boolean containsExitListener(IBinder token) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                return impl != null && impl.mExitAnimationListener != null;
+            }
+        }
+    }
+}
diff --git a/core/java/android/window/SplashScreenView.aidl b/core/java/android/window/SplashScreenView.aidl
new file mode 100644
index 0000000..cc7ac1e
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/** @hide */
+parcelable SplashScreenView.SplashScreenViewParcelable;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
new file mode 100644
index 0000000..35ccfca
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.window;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.policy.DecorView;
+
+/**
+ * <p>The view which allows an activity to customize its splash screen exit animation.</p>
+ *
+ * <p>Activities will receive this view as a parameter of
+ * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if
+ * they set {@link SplashScreen#setOnExitAnimationListener}.
+ * When this callback is called, this view will be on top of the activity.</p>
+ *
+ * <p>This view is composed of a view containing the splashscreen icon (see
+ * windowSplashscreenAnimatedIcon) and a background.
+ * Developers can use {@link #getIconView} to get this view and replace the drawable or
+ * add animation to it. The background of this view is filled with a single color, which can be
+ * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p>
+ *
+ * @see SplashScreen
+ */
+public final class SplashScreenView extends FrameLayout {
+    private static final String TAG = SplashScreenView.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private boolean mNotCopyable;
+    private int mInitBackgroundColor;
+    private View mIconView;
+    private Bitmap mParceledIconBitmap;
+    private View mBrandingImageView;
+    private Bitmap mParceledBrandingBitmap;
+    private long mIconAnimationDuration;
+    private long mIconAnimationStart;
+
+    private Animatable mAnimatableIcon;
+    private ValueAnimator mAnimator;
+
+    // cache original window and status
+    private Window mWindow;
+    private boolean mDrawBarBackground;
+    private int mStatusBarColor;
+    private int mNavigationBarColor;
+
+    /**
+     * Internal builder to create a SplashScreenWindowView object.
+     * @hide
+     */
+    public static class Builder {
+        private final Context mContext;
+        private int mIconSize;
+        private @ColorInt int mBackgroundColor;
+        private Bitmap mParceledIconBitmap;
+        private Drawable mIconDrawable;
+        private int mBrandingImageWidth;
+        private int mBrandingImageHeight;
+        private Drawable mBrandingDrawable;
+        private Bitmap mParceledBrandingBitmap;
+        private long mIconAnimationStart;
+        private long mIconAnimationDuration;
+
+        public Builder(@NonNull Context context) {
+            mContext = context;
+        }
+
+        /**
+         * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so
+         * you do not need to call other set methods.
+         */
+        public Builder createFromParcel(SplashScreenViewParcelable parcelable) {
+            mIconSize = parcelable.getIconSize();
+            mBackgroundColor = parcelable.getBackgroundColor();
+            if (parcelable.mIconBitmap != null) {
+                mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap);
+                mParceledIconBitmap = parcelable.mIconBitmap;
+            }
+            if (parcelable.mBrandingBitmap != null) {
+                setBrandingDrawable(new BitmapDrawable(mContext.getResources(),
+                                parcelable.mBrandingBitmap), parcelable.mBrandingWidth,
+                        parcelable.mBrandingHeight);
+                mParceledBrandingBitmap = parcelable.mBrandingBitmap;
+            }
+            mIconAnimationStart = parcelable.mIconAnimationStart;
+            mIconAnimationDuration = parcelable.mIconAnimationDuration;
+            return this;
+        }
+
+        /**
+         * Set the rectangle size for the center view.
+         */
+        public Builder setIconSize(int iconSize) {
+            mIconSize = iconSize;
+            return this;
+        }
+
+        /**
+         * Set the background color for the view.
+         */
+        public Builder setBackgroundColor(@ColorInt int backgroundColor) {
+            mBackgroundColor = backgroundColor;
+            return this;
+        }
+
+        /**
+         * Set the Drawable object to fill the center view.
+         */
+        public Builder setCenterViewDrawable(Drawable drawable) {
+            mIconDrawable = drawable;
+            return this;
+        }
+
+        /**
+         * Set the animation duration if icon is animatable.
+         */
+        public Builder setAnimationDuration(int duration) {
+            mIconAnimationDuration = duration;
+            return this;
+        }
+
+        /**
+         * Set the Drawable object and size for the branding view.
+         */
+        public Builder setBrandingDrawable(Drawable branding, int width, int height) {
+            mBrandingDrawable = branding;
+            mBrandingImageWidth = width;
+            mBrandingImageHeight = height;
+            return this;
+        }
+
+        /**
+         * Create SplashScreenWindowView object from materials.
+         */
+        public SplashScreenView build() {
+            final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+            final SplashScreenView view = (SplashScreenView)
+                    layoutInflater.inflate(R.layout.splash_screen_view, null, false);
+            view.mInitBackgroundColor = mBackgroundColor;
+            view.setBackgroundColor(mBackgroundColor);
+            view.mIconView = view.findViewById(R.id.splashscreen_icon_view);
+            view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
+            // center icon
+            if (mIconSize != 0) {
+                final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams();
+                params.width = mIconSize;
+                params.height = mIconSize;
+                view.mIconView.setLayoutParams(params);
+            }
+            if (mIconDrawable != null) {
+                view.mIconView.setBackground(mIconDrawable);
+                view.initIconAnimation(mIconDrawable, mIconAnimationDuration);
+            }
+            view.mIconAnimationStart = mIconAnimationStart;
+            view.mIconAnimationDuration = mIconAnimationDuration;
+            if (mParceledIconBitmap != null) {
+                view.mParceledIconBitmap = mParceledIconBitmap;
+            }
+            // branding image
+            if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+                final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
+                params.width = mBrandingImageWidth;
+                params.height = mBrandingImageHeight;
+                view.mBrandingImageView.setLayoutParams(params);
+            }
+            if (mBrandingDrawable != null) {
+                view.mBrandingImageView.setBackground(mBrandingDrawable);
+            }
+            if (mParceledBrandingBitmap != null) {
+                view.mParceledBrandingBitmap = mParceledBrandingBitmap;
+            }
+            if (DEBUG) {
+                Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: "
+                        + mIconDrawable + " size: " + mIconSize + "\n Branding: view: "
+                        + view.mBrandingImageView + " drawable: " + mBrandingDrawable
+                        + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
+            }
+            return view;
+        }
+    }
+
+    /** @hide */
+    public SplashScreenView(Context context) {
+        super(context);
+    }
+
+    /** @hide */
+    public SplashScreenView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+    }
+
+    /**
+     * Declared this view is not copyable.
+     * @hide
+     */
+    public void setNotCopyable() {
+        mNotCopyable = true;
+    }
+
+    /**
+     * Whether this view is copyable.
+     * @hide
+     */
+    public boolean isCopyable() {
+        return !mNotCopyable;
+    }
+
+    /**
+     * Returns the duration of the icon animation if icon is animatable.
+     *
+     * @see android.R.attr#windowSplashScreenAnimatedIcon
+     * @see android.R.attr#windowSplashScreenAnimationDuration
+     */
+    public long getIconAnimationDurationMillis() {
+        return mIconAnimationDuration;
+    }
+
+    /**
+     * If the replaced icon is animatable, return the animation start time in millisecond based on
+     * system. The start time is set using {@link SystemClock#uptimeMillis()}.
+     */
+    public long getIconAnimationStartMillis() {
+        return mIconAnimationStart;
+    }
+
+    void initIconAnimation(Drawable iconDrawable, long duration) {
+        if (iconDrawable instanceof Animatable) {
+            mAnimatableIcon = (Animatable) iconDrawable;
+            mAnimator = ValueAnimator.ofInt(0, 1);
+            mAnimator.setDuration(duration);
+            mAnimator.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIconAnimationStart = SystemClock.uptimeMillis();
+                    mAnimatableIcon.start();
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAnimatableIcon.stop();
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mAnimatableIcon.stop();
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+                    // do not repeat
+                    mAnimatableIcon.stop();
+                }
+            });
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void startIntroAnimation() {
+        if (mAnimatableIcon != null) {
+            mAnimator.start();
+        }
+    }
+
+    /**
+     * <p>Remove this view and release its resource. </p>
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     */
+    public void remove() {
+        setVisibility(GONE);
+        if (mParceledIconBitmap != null) {
+            mIconView.setBackground(null);
+            mParceledIconBitmap.recycle();
+            mParceledIconBitmap = null;
+        }
+        if (mParceledBrandingBitmap != null) {
+            mBrandingImageView.setBackground(null);
+            mParceledBrandingBitmap.recycle();
+            mParceledBrandingBitmap = null;
+        }
+        if (mWindow != null) {
+            final DecorView decorView = (DecorView) mWindow.peekDecorView();
+            if (DEBUG) {
+                Log.d(TAG, "remove starting view");
+            }
+            if (decorView != null) {
+                decorView.removeView(this);
+            }
+            restoreSystemUIColors();
+            mWindow = null;
+        }
+    }
+
+    /**
+     * Cache the root window.
+     * @hide
+     */
+    public void cacheRootWindow(Window window) {
+        mWindow = window;
+    }
+
+    /**
+     * Called after SplashScreenView has added on the root window.
+     * @hide
+     */
+    public void makeSystemUIColorsTransparent() {
+        if (mWindow != null) {
+            final WindowManager.LayoutParams attr = mWindow.getAttributes();
+            mDrawBarBackground = (attr.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+            mWindow.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            mStatusBarColor = mWindow.getStatusBarColor();
+            mNavigationBarColor = mWindow.getNavigationBarDividerColor();
+            mWindow.setStatusBarColor(Color.TRANSPARENT);
+            mWindow.setNavigationBarColor(Color.TRANSPARENT);
+        }
+        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+    }
+
+    private void restoreSystemUIColors() {
+        if (mWindow != null) {
+            if (!mDrawBarBackground) {
+                mWindow.clearFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            }
+            mWindow.setStatusBarColor(mStatusBarColor);
+            mWindow.setNavigationBarColor(mNavigationBarColor);
+        }
+    }
+
+    /**
+     * Get the view containing the Splash Screen icon and its background.
+     * @see android.R.attr#windowSplashScreenAnimatedIcon
+     */
+    public @Nullable View getIconView() {
+        return mIconView;
+    }
+
+    /**
+     * Get the branding image view.
+     * @hide
+     */
+    @TestApi
+    public @Nullable View getBrandingView() {
+        return mBrandingImageView;
+    }
+
+    /**
+     * Get the initial background color of this view.
+     * @hide
+     */
+    @ColorInt int getInitBackgroundColor() {
+        return mInitBackgroundColor;
+    }
+
+    /**
+     * Use to create {@link SplashScreenView} object across process.
+     * @hide
+     */
+    public static class SplashScreenViewParcelable implements Parcelable {
+        private int mIconSize;
+        private int mBackgroundColor;
+
+        private Bitmap mIconBitmap;
+        private int mBrandingWidth;
+        private int mBrandingHeight;
+        private Bitmap mBrandingBitmap;
+
+        private long mIconAnimationStart;
+        private long mIconAnimationDuration;
+
+        public SplashScreenViewParcelable(SplashScreenView view) {
+            ViewGroup.LayoutParams params = view.getIconView().getLayoutParams();
+            mIconSize = params.height;
+            mBackgroundColor = view.getInitBackgroundColor();
+
+            mIconBitmap = copyDrawable(view.getIconView().getBackground());
+            mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
+            params = view.getBrandingView().getLayoutParams();
+            mBrandingWidth = params.width;
+            mBrandingHeight = params.height;
+
+            mIconAnimationStart = view.getIconAnimationStartMillis();
+            mIconAnimationDuration = view.getIconAnimationDurationMillis();
+        }
+
+        private Bitmap copyDrawable(Drawable drawable) {
+            if (drawable != null) {
+                final Rect initialBounds = drawable.copyBounds();
+                final int width = initialBounds.width();
+                final int height = initialBounds.height();
+
+                final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                final Canvas bmpCanvas = new Canvas(snapshot);
+                drawable.setBounds(0, 0, width, height);
+                drawable.draw(bmpCanvas);
+                final Bitmap copyBitmap = snapshot.createAshmemBitmap();
+                snapshot.recycle();
+                return copyBitmap;
+            }
+            return null;
+        }
+
+        private SplashScreenViewParcelable(@NonNull Parcel source) {
+            readParcel(source);
+        }
+
+        private void readParcel(@NonNull Parcel source) {
+            mIconSize = source.readInt();
+            mBackgroundColor = source.readInt();
+            mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
+            mBrandingWidth = source.readInt();
+            mBrandingHeight = source.readInt();
+            mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
+            mIconAnimationStart = source.readLong();
+            mIconAnimationDuration = source.readLong();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mIconSize);
+            dest.writeInt(mBackgroundColor);
+            dest.writeTypedObject(mIconBitmap, flags);
+            dest.writeInt(mBrandingWidth);
+            dest.writeInt(mBrandingHeight);
+            dest.writeTypedObject(mBrandingBitmap, flags);
+            dest.writeLong(mIconAnimationStart);
+            dest.writeLong(mIconAnimationDuration);
+        }
+
+        public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
+                new Parcelable.Creator<SplashScreenViewParcelable>() {
+                    public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
+                        return new SplashScreenViewParcelable(source);
+                    }
+                    public SplashScreenViewParcelable[] newArray(int size) {
+                        return new SplashScreenViewParcelable[size];
+                    }
+                };
+
+        /**
+         * Release the bitmap if another process cannot handle it.
+         */
+        public void clearIfNeeded() {
+            if (mIconBitmap != null) {
+                mIconBitmap.recycle();
+                mIconBitmap = null;
+            }
+            if (mBrandingBitmap != null) {
+                mBrandingBitmap.recycle();
+                mBrandingBitmap = null;
+            }
+        }
+
+        int getIconSize() {
+            return mIconSize;
+        }
+
+        int getBackgroundColor() {
+            return mBackgroundColor;
+        }
+    }
+}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 2282cc5..63b9e9b 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -95,6 +95,12 @@
      */
     public int startingWindowTypeParameter;
 
+    /**
+     * Specifies a theme for the splash screen.
+     * @hide
+     */
+    public int splashScreenThemeResId;
+
     public StartingWindowInfo() {
 
     }
@@ -115,6 +121,7 @@
         dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
         dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
         dest.writeTypedObject(mainWindowLayoutParams, flags);
+        dest.writeInt(splashScreenThemeResId);
     }
 
     void readFromParcel(@NonNull Parcel source) {
@@ -124,6 +131,7 @@
         topOpaqueWindowLayoutParams = source.readTypedObject(
                 WindowManager.LayoutParams.CREATOR);
         mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
+        splashScreenThemeResId = source.readInt();
     }
 
     @Override
@@ -135,7 +143,8 @@
                 + Integer.toHexString(startingWindowTypeParameter)
                 + " insetsState=" + topOpaqueWindowInsetsState
                 + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams
-                + " mainWindowLayoutParams=" + mainWindowLayoutParams;
+                + " mainWindowLayoutParams=" + mainWindowLayoutParams
+                + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId);
     }
 
     public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index cdb4762..217ade8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -105,6 +105,12 @@
     public void removeStartingWindow(int taskId) {}
 
     /**
+     * Called when the Task want to copy the splash screen.
+     */
+    @BinderThread
+    public void copySplashScreenView(int taskId) {}
+
+    /**
      * Called when a task with the registered windowing mode can be controlled by this task
      * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer
      * to show this task.
@@ -223,6 +229,11 @@
         }
 
         @Override
+        public void copySplashScreenView(int taskId) {
+            mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId));
+        }
+
+        @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
             mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
         }
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
new file mode 100644
index 0000000..5d02a29
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.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 com.android.internal.compat;
+
+parcelable CompatibilityOverrideConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
new file mode 100644
index 0000000..1c222a7
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.compat;
+
+
+import android.app.compat.PackageOverride;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityOverrideConfig implements Parcelable {
+    public final Map<Long, PackageOverride> overrides;
+
+    public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) {
+        this.overrides = overrides;
+    }
+
+    private CompatibilityOverrideConfig(Parcel in) {
+        int keyCount = in.readInt();
+        overrides = new HashMap<>();
+        for (int i = 0; i < keyCount; i++) {
+            long key = in.readLong();
+            PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader());
+            overrides.put(key, override);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(overrides.size());
+        for (Long key : overrides.keySet()) {
+            dest.writeLong(key);
+            dest.writeParcelable(overrides.get(key), 0);
+        }
+    }
+
+    public static final Creator<CompatibilityOverrideConfig> CREATOR =
+            new Creator<CompatibilityOverrideConfig>() {
+
+                @Override
+                public CompatibilityOverrideConfig createFromParcel(Parcel in) {
+                    return new CompatibilityOverrideConfig(in);
+                }
+
+                @Override
+                public CompatibilityOverrideConfig[] newArray(int size) {
+                    return new CompatibilityOverrideConfig[size];
+                }
+            };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7aca36a..249c134 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -21,6 +21,7 @@
 import java.util.Map;
 
 parcelable CompatibilityChangeConfig;
+parcelable CompatibilityOverrideConfig;
 parcelable CompatibilityChangeInfo;
 /**
  * Platform private API for talking with the PlatformCompat service.
@@ -152,6 +153,17 @@
     /**
      * Adds overrides to compatibility changes.
      *
+     * <p>Kills the app to allow the changes to take effect.
+     *
+     * @param overrides   parcelable containing the compat change overrides to be applied
+     * @param packageName the package name of the app whose changes will be overridden
+     * @throws SecurityException if overriding changes is not permitted
+     */
+    void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName);
+
+    /**
+     * Adds overrides to compatibility changes.
+     *
      * <p>Does not kill the app, to be only used in tests.
      *
      * @param overrides   parcelable containing the compat change overrides to be applied
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 0ede1b8..e602cd2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -28,6 +28,7 @@
 import android.database.MatrixCursor.RowBuilder;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.FileObserver;
@@ -504,7 +505,7 @@
 
         final int pfdMode = ParcelFileDescriptor.parseMode(mode);
         if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
-            return ParcelFileDescriptor.open(file, pfdMode);
+            return openFileForRead(file);
         } else {
             try {
                 // When finished writing, kick off media scanner
@@ -519,6 +520,24 @@
         }
     }
 
+    private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException {
+        final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target);
+
+        // Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to
+        // transcode or not transcode can be made based upon the calling app's uid, and not based
+        // upon the Provider's uid.
+        final Bundle opts = new Bundle();
+        opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, Binder.getCallingUid());
+
+        final AssetFileDescriptor afd =
+                getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts);
+        if (afd == null) {
+            return null;
+        }
+
+        return afd.getParcelFileDescriptor();
+    }
+
     /**
      * Test if the file matches the query arguments.
      *
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index dfcc914..1c7eca8 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -20,6 +20,8 @@
 import android.graphics.fonts.FontUpdateRequest;
 import android.text.FontConfig;
 
+import java.util.List;
+
 /**
  * System private interface for talking with
  * {@link com.android.server.graphics.fonts.FontManagerService}.
@@ -28,5 +30,7 @@
 interface IFontManager {
     FontConfig getFontConfig();
 
-    int updateFont(int baseVersion, in FontUpdateRequest request);
+    int updateFontFile(in FontUpdateRequest request, int baseVersion);
+
+    int updateFontFamily(in List<FontUpdateRequest> request, int baseVersion);
 }
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index 9b88516..4f2f973b 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -135,6 +135,11 @@
     public double systemServiceCpuPowerMah;
     public double[] customMeasuredPowerMah;
 
+    // Power that is re-attributed to other sippers. For example, for System Server
+    // this represents the power attributed to apps requesting system services.
+    // The value should be negative or zero.
+    public double powerReattributedToOtherSippersMah;
+
     // Do not include this sipper in results because it is included
     // in an aggregate sipper.
     public boolean isAggregated;
@@ -263,6 +268,7 @@
                 }
             }
         }
+        powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah;
     }
 
     /**
@@ -281,6 +287,10 @@
                 totalPowerMah += customMeasuredPowerMah[idx];
             }
         }
+
+        // powerAttributedToOtherSippersMah is negative or zero
+        totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah;
+
         totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
 
         return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a39d19e..87820a8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1002,8 +1002,12 @@
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     /**
-     * Accumulated energy consumption, that is not attributed to individual uids, of various
-     * consumers while on battery.
+     * Accumulated global (generally, device-wide total) energy consumption of various consumers
+     * while on battery.
+     * Its '<b>custom</b> energy buckets' correspond to the
+     * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+     * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+     *
      * If energy consumer data is completely unavailable this will be null.
      */
     @GuardedBy("this")
@@ -7533,9 +7537,16 @@
          */
         private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
 
-        /** Measured energies attributed to this uid while on battery. */
-        // We do not use a SparseArray<LongSamplingCounters> since it would cause lots of
-        // unnecessary timebase references, and we're just going to use on-battery anyway...
+        /**
+         * Measured energies attributed to this uid while on battery.
+         * Its '<b>custom</b> energy buckets' correspond to the
+         * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+         * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+         *
+         * Will be null if energy consumer data is completely unavailable (in which case
+         * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid
+         * is 0 for every bucket.
+         */
         private MeasuredEnergyStats mUidMeasuredEnergyStats;
 
         /**
@@ -12523,11 +12534,8 @@
             final int uidInt = mapUid(uidEnergies.keyAt(i));
             final long uidEnergyUJ = uidEnergies.valueAt(i);
             if (uidEnergyUJ == 0) continue;
-            // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
-            //  Specifically: What if the uid had been removed? We'll re-create it now.
-            //  And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
-            //  not create a Uid even when we should be (say, the app's first event, somehow, was to
-            //  use GPU). I guess that CPU/kernel data might already have this problem?
+            // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this,
+            //  or converse problem of not creating a new Uid if its first blame is recorded here.
             final Uid uidObj = getUidStatsLocked(uidInt);
             uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
         }
@@ -14541,7 +14549,7 @@
             return;
         }
 
-        dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
+        dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats);
 
         int size = mUidStats.size();
         for (int i = 0; i < size; i++) {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 2798b72..233ba19 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -70,11 +70,14 @@
                 mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
-                mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
-
                 mPowerCalculators.add(new UserPowerCalculator());
+
+                // It is important that SystemServicePowerCalculator be applied last,
+                // because it re-attributes some of the power estimated by the other
+                // calculators.
+                mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
             }
         }
         return mPowerCalculators;
@@ -128,7 +131,8 @@
         final long uptimeUs = SystemClock.uptimeMillis() * 1000;
 
         final List<PowerCalculator> powerCalculators = getPowerCalculators();
-        for (PowerCalculator powerCalculator : powerCalculators) {
+        for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+            PowerCalculator powerCalculator = powerCalculators.get(i);
             powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
                     query);
         }
diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
new file mode 100644
index 0000000..2852547
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -0,0 +1,26 @@
+/*
+ * 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.internal.os;
+
+/** CPU tracking using eBPF. */
+public final class KernelCpuBpfTracking {
+    private KernelCpuBpfTracking() {
+    }
+
+    /** Returns whether CPU tracking using eBPF is supported. */
+    public static native boolean isSupported();
+}
diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
index 50331e3..06760e1 100644
--- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
+++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
@@ -23,9 +23,6 @@
     private KernelCpuTotalBpfMapReader() {
     }
 
-    /** Returns whether total CPU time is measured. */
-    public static native boolean isSupported();
-
     /** Reads total CPU time from bpf map. */
     public static native boolean read(Callback callback);
 
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 955f6af..5c0eeb2 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -20,12 +20,12 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -36,87 +36,112 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "SystemServicePowerCalc";
 
-    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
-
-    private final PowerProfile mPowerProfile;
-
-    // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
-    // Data organized like this:
+    // Power estimators per CPU cluster, per CPU frequency. The array is flattened according
+    // to this layout:
     // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
-    private double[] mSystemServicePowerMaUs;
+    private final UsageBasedPowerEstimator[] mPowerEstimators;
 
     public SystemServicePowerCalculator(PowerProfile powerProfile) {
-        mPowerProfile = powerProfile;
+        int numFreqs = 0;
+        final int numCpuClusters = powerProfile.getNumCpuClusters();
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+        }
+
+        mPowerEstimators = new UsageBasedPowerEstimator[numFreqs];
+        int index = 0;
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int speed = 0; speed < numSpeeds; speed++) {
+                mPowerEstimators[index++] = new UsageBasedPowerEstimator(
+                        powerProfile.getAveragePowerForCpuCore(cluster, speed));
+            }
+        }
     }
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        calculateSystemServicePower(batteryStats);
-        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
-    }
+        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        final UidBatteryConsumer.Builder systemServerConsumer = uidBatteryConsumerBuilders.get(
+                Process.SYSTEM_UID);
 
-    @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
-                calculateSystemServerCpuPowerMah(u));
+        if (systemServerConsumer != null) {
+            systemServicePowerMah = Math.min(systemServicePowerMah,
+                    systemServerConsumer.getTotalPower());
+
+            // The system server power needs to be adjusted because part of it got
+            // distributed to applications
+            systemServerConsumer.setConsumedPower(
+                    BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
+                    -systemServicePowerMah);
+        }
+
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            if (app != systemServerConsumer) {
+                final BatteryStats.Uid uid = app.getBatteryStatsUid();
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+                        systemServicePowerMah * uid.getProportionalSystemServiceUsage());
+            }
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType,
             SparseArray<UserHandle> asUsers) {
-        calculateSystemServicePower(batteryStats);
-        super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+        BatterySipper systemServerSipper = null;
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                if (app.getUid() == Process.SYSTEM_UID) {
+                    systemServerSipper = app;
+                    break;
+                }
+            }
+        }
+
+        if (systemServerSipper != null) {
+            systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower());
+
+            // The system server power needs to be adjusted because part of it got
+            // distributed to applications
+            systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah;
+        }
+
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                if (app != systemServerSipper) {
+                    final BatteryStats.Uid uid = app.uidObj;
+                    app.systemServiceCpuPowerMah =
+                            systemServicePowerMah * uid.getProportionalSystemServiceUsage();
+                }
+            }
+        }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        app.systemServiceCpuPowerMah = calculateSystemServerCpuPowerMah(u);
-    }
-
-    private void calculateSystemServicePower(BatteryStats batteryStats) {
+    private double calculateSystemServicePower(BatteryStats batteryStats) {
         final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds();
         if (systemServiceTimeAtCpuSpeeds == null) {
-            return;
+            return 0;
         }
 
-        if (mSystemServicePowerMaUs == null) {
-            mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length];
-        }
-        int index = 0;
-        final int numCpuClusters = mPowerProfile.getNumCpuClusters();
-        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-            final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
-            for (int speed = 0; speed < numSpeeds; speed++) {
-                mSystemServicePowerMaUs[index] =
-                        systemServiceTimeAtCpuSpeeds[index]
-                                * mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
-                index++;
-            }
+        // TODO(179210707): additionally account for CPU active and per cluster battery use
+
+        double powerMah = 0;
+        for (int i = 0; i < mPowerEstimators.length; i++) {
+            powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i]);
         }
 
         if (DEBUG) {
-            Log.d(TAG, "System service power per CPU cluster and frequency:"
-                    + Arrays.toString(mSystemServicePowerMaUs));
+            Log.d(TAG, "System service power:" + powerMah);
         }
-    }
 
-    private double calculateSystemServerCpuPowerMah(BatteryStats.Uid u) {
-        double cpuPowerMaUs = 0;
-        final double proportionalUsage = u.getProportionalSystemServiceUsage();
-        if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) {
-            for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
-                cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
-            }
-        }
-        return cpuPowerMaUs / MICROSEC_IN_HR;
-    }
-
-    @Override
-    public void reset() {
-        mSystemServicePowerMaUs = null;
+        return powerMah;
     }
 }
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 3f68597..0f4767b 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -15,7 +15,12 @@
  */
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -26,39 +31,93 @@
 public class WakelockPowerCalculator extends PowerCalculator {
     private static final String TAG = "WakelockPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
-    private final double mPowerWakelock;
-    private long mTotalAppWakelockTimeMs = 0;
+    private final UsageBasedPowerEstimator mPowerEstimator;
+
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
 
     public WakelockPowerCalculator(PowerProfile profile) {
-        mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
+        mPowerEstimator = new UsageBasedPowerEstimator(
+                profile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
+    }
+
+    @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration result = new PowerAndDuration();
+        UidBatteryConsumer.Builder osBatteryConsumer = null;
+        double osPowerMah = 0;
+        long osDurationMs = 0;
+        long totalAppDurationMs = 0;
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(result, app.getBatteryStatsUid(), rawRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK, result.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+            totalAppDurationMs += result.durationMs;
+
+            if (app.getUid() == Process.ROOT_UID) {
+                osBatteryConsumer = app;
+                osDurationMs = result.durationMs;
+                osPowerMah = result.powerMah;
+            }
+        }
+
+        // The device has probably been awake for longer than the screen on
+        // time and application wake lock time would account for.  Assign
+        // this remainder to the OS, if possible.
+        if (osBatteryConsumer != null) {
+            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
+            osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK,
+                    result.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        final PowerAndDuration result = new PowerAndDuration();
+        BatterySipper osSipper = null;
+        double osPowerMah = 0;
+        long osDurationMs = 0;
+        long totalAppDurationMs = 0;
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                calculateApp(result, app.uidObj, rawRealtimeUs, statsType);
+                app.wakeLockTimeMs = result.durationMs;
+                app.wakeLockPowerMah = result.powerMah;
+                totalAppDurationMs += result.durationMs;
+
+                if (app.getUid() == Process.ROOT_UID) {
+                    osSipper = app;
+                    osPowerMah = result.powerMah;
+                    osDurationMs = result.durationMs;
+                }
+            }
+        }
 
         // The device has probably been awake for longer than the screen on
         // time and application wake lock time would account for.  Assign
         // this remainder to the OS, if possible.
-        BatterySipper osSipper = null;
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            BatterySipper app = sippers.get(i);
-            if (app.getUid() == 0) {
-                osSipper = app;
-                break;
-            }
-        }
-
         if (osSipper != null) {
-            calculateRemaining(osSipper, batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
+            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType,
+                    osPowerMah, osDurationMs, totalAppDurationMs);
+            osSipper.wakeLockTimeMs = result.durationMs;
+            osSipper.wakeLockPowerMah = result.powerMah;
             osSipper.sumPower();
         }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
+    private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs,
+            int statsType) {
         long wakeLockTimeUs = 0;
         final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
                 u.getWakelockStats();
@@ -73,34 +132,29 @@
                 wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
             }
         }
-        app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
-        mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+        result.durationMs = wakeLockTimeUs / 1000; // convert to millis
 
         // Add cost of holding a wake lock.
-        app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000 * 60 * 60);
-        if (DEBUG && app.wakeLockPowerMah != 0) {
-            Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
-                    + " power=" + formatCharge(app.wakeLockPowerMah));
+        result.powerMah = mPowerEstimator.calculatePower(result.durationMs);
+        if (DEBUG && result.powerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs
+                    + " power=" + formatCharge(result.powerMah));
         }
     }
 
-    private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
-        wakeTimeMillis -= mTotalAppWakelockTimeMs
-                + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+    private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs,
+            long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs,
+            long totalAppDurationMs) {
+        final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000
+                - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000
+                - totalAppDurationMs;
         if (wakeTimeMillis > 0) {
-            final double power = (wakeTimeMillis * mPowerWakelock) / (1000 * 60 * 60);
+            final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
             if (DEBUG) {
                 Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power));
             }
-            app.wakeLockTimeMs += wakeTimeMillis;
-            app.wakeLockPowerMah += power;
+            result.durationMs = osDurationMs + wakeTimeMillis;
+            result.powerMah = osPowerMah + power;
         }
     }
-
-    @Override
-    public void reset() {
-        mTotalAppWakelockTimeMs = 0;
-    }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adebde0..9840013 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -110,6 +110,7 @@
 import android.widget.PopupWindow;
 
 import com.android.internal.R;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.policy.PhoneWindow.PanelFeatureState;
 import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback;
 import com.android.internal.view.FloatingActionMode;
@@ -255,6 +256,7 @@
     private Drawable mOriginalBackgroundDrawable;
     private Drawable mLastOriginalBackgroundDrawable;
     private Drawable mResizingBackgroundDrawable;
+    private BackgroundBlurDrawable mBackgroundBlurDrawable;
 
     /**
      * Temporary holder for a window background when it is set before {@link #mWindow} is
@@ -280,9 +282,14 @@
     private final Paint mLegacyNavigationBarBackgroundPaint = new Paint();
     private Insets mBackgroundInsets = Insets.NONE;
     private Insets mLastBackgroundInsets = Insets.NONE;
+    private int mLastBackgroundBlurRadius = 0;
     private boolean mDrawLegacyNavigationBarBackground;
 
     private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
+    private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> {
+        updateBackgroundBlur();
+        return true;
+    };
 
     DecorView(Context context, int featureId, PhoneWindow window,
             WindowManager.LayoutParams params) {
@@ -1263,18 +1270,27 @@
         if (mBackgroundInsets == null) {
             mBackgroundInsets = Insets.NONE;
         }
+
         if (mBackgroundInsets.equals(mLastBackgroundInsets)
+                && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius
                 && mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
             return;
         }
-        if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) {
 
-            // Call super since we are intercepting setBackground on this class.
-            super.setBackgroundDrawable(mOriginalBackgroundDrawable);
-        } else {
+        Drawable destDrawable = mOriginalBackgroundDrawable;
+        if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null
+                && mWindow.isTranslucent()) {
+            if (mBackgroundBlurDrawable == null) {
+                mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
+            }
+            destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable,
+                                                             mOriginalBackgroundDrawable});
+            mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius;
+        }
 
-            // Call super since we are intercepting setBackground on this class.
-            super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable,
+
+        if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
+            destDrawable = new InsetDrawable(destDrawable,
                     mBackgroundInsets.left, mBackgroundInsets.top,
                     mBackgroundInsets.right, mBackgroundInsets.bottom) {
 
@@ -1286,12 +1302,32 @@
                 public boolean getPadding(Rect padding) {
                     return getDrawable().getPadding(padding);
                 }
-            });
+            };
         }
+
+        // Call super since we are intercepting setBackground on this class.
+        super.setBackgroundDrawable(destDrawable);
+
         mLastBackgroundInsets = mBackgroundInsets;
         mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
     }
 
+    private void updateBackgroundBlur() {
+        if (mBackgroundBlurDrawable == null) return;
+
+        // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't
+        // need to calculate the corner radius.
+        if (mWindow.mBackgroundBlurRadius > 0) {
+            if (mOriginalBackgroundDrawable != null) {
+                final Outline outline = new Outline();
+                mOriginalBackgroundDrawable.getOutline(outline);
+                mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT
+                                                           ? outline.getRadius() : 0);
+            }
+        }
+        mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius);
+    }
+
     @Override
     public Drawable getBackground() {
         return mOriginalBackgroundDrawable;
@@ -1722,6 +1758,9 @@
             cb.onAttachedToWindow();
         }
 
+        getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+        updateBackgroundDrawable();
+
         if (mFeatureId == -1) {
             /*
              * The main window has been attached, try to restore any panels
@@ -1755,6 +1794,8 @@
             cb.onDetachedFromWindow();
         }
 
+        getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+
         if (mWindow.mDecorContentParent != null) {
             mWindow.mDecorContentParent.dismissPopups();
         }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 5df175e..d06413c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -258,6 +258,8 @@
     Drawable mBackgroundDrawable = null;
     Drawable mBackgroundFallbackDrawable = null;
 
+    int mBackgroundBlurRadius = 0;
+
     private boolean mLoadElevation = true;
     private float mElevation;
 
@@ -1523,6 +1525,15 @@
     }
 
     @Override
+    public final void setBackgroundBlurRadius(int blurRadius) {
+        super.setBackgroundBlurRadius(blurRadius);
+        if (getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_CROSS_LAYER_BLUR)) {
+            mBackgroundBlurRadius = Math.max(blurRadius, 0);
+        }
+    }
+
+    @Override
     public final void setFeatureDrawableResource(int featureId, int resId) {
         if (resId != 0) {
             DrawableFeatureState st = getDrawableState(featureId, true);
@@ -2549,6 +2560,9 @@
                     android.R.styleable.Window_windowBlurBehindRadius, 0);
         }
 
+        setBackgroundBlurRadius(a.getDimensionPixelSize(
+                R.styleable.Window_windowBackgroundBlurRadius, 0));
+
 
         if (params.windowAnimations == 0) {
             params.windowAnimations = a.getResourceId(
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9510f27..6983d35 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -184,6 +184,7 @@
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
+                "com_android_internal_os_KernelCpuBpfTracking.cpp",
                 "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
                 "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
                 "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8879111..38bcc0f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -190,6 +190,7 @@
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
 extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
@@ -1586,6 +1587,7 @@
         REG_JNI(register_android_security_Scrypt),
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
+        REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking),
         REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader),
         REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
         REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index a7950f2..8dcb210 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -829,6 +829,16 @@
     return dmabufTotalSizeKb;
 }
 
+static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufHeapTotalSizeKb = -1;
+    uint64_t size;
+
+    if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) {
+        dmabufHeapTotalSizeKb = size;
+    }
+    return dmabufHeapTotalSizeKb;
+}
+
 static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
     jlong poolsSizeKb = -1;
     uint64_t size;
@@ -986,6 +996,8 @@
             (void*)android_os_Debug_getDmabufTotalExportedKb },
     { "getGpuDmaBufUsageKb", "()J",
             (void*)android_os_Debug_getGpuDmaBufUsageKb },
+    { "getDmabufHeapTotalExportedKb", "()J",
+            (void*)android_os_Debug_getDmabufHeapTotalExportedKb },
     { "getIonPoolsSizeKb", "()J",
             (void*)android_os_Debug_getIonPoolsSizeKb },
     { "getDmabufMappedSizeKb", "()J",
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index c08363b..dcfa950 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -377,6 +377,10 @@
     return (int) sp;
 }
 
+jint android_os_Process_createProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) {
+    return createProcessGroup(uid, pid);
+}
+
 /** Sample CPUset list format:
  *  0-3,4,6-8
  */
@@ -1358,6 +1362,7 @@
         {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
         {"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
         {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
+        {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup},
         {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores},
         {"setSwappiness", "(IZ)Z", (void*)android_os_Process_setSwappiness},
         {"setArgV0", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
new file mode 100644
index 0000000..e6a82f6
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static jboolean KernelCpuBpfTracking_isSupported(JNIEnv *, jobject) {
+    return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
+}
+
+static const JNINativeMethod methods[] = {
+        {"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported},
+};
+
+int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/KernelCpuBpfTracking", methods,
+                                NELEM(methods));
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
index d8446ca..7249238 100644
--- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
@@ -20,10 +20,6 @@
 
 namespace android {
 
-static jboolean KernelCpuTotalBpfMapReader_isSupported(JNIEnv *, jobject) {
-    return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
-}
-
 static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) {
     jclass callbackClass = env->GetObjectClass(callback);
     jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V");
@@ -51,7 +47,6 @@
 static const JNINativeMethod methods[] = {
         {"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z",
          (void *)KernelCpuTotalBpfMapReader_read},
-        {"isSupported", "()Z", (void *)KernelCpuTotalBpfMapReader_isSupported},
 };
 
 int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) {
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
new file mode 100644
index 0000000..aab054f
--- /dev/null
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.server.vibrator;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message OneShotProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 duration = 1;
+    repeated int32 amplitude = 2;
+}
+
+message WaveformProto {
+   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+   repeated int32 timings = 1;
+   repeated int32 amplitudes = 2;
+   required bool repeat = 3;
+}
+
+message PrebakedProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 effect_id = 1;
+    optional int32 effect_strength = 2;
+    optional int32 fallback = 3;
+}
+
+message ComposedProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 effect_ids = 1;
+    repeated float effect_scales = 2;
+    repeated int32 delays = 3;
+}
+
+// A com.android.os.VibrationEffect object.
+message VibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional OneShotProto oneshot = 1;
+    optional WaveformProto waveform = 2;
+    optional PrebakedProto prebaked = 3;
+    optional ComposedProto composed = 4;
+}
+
+message SyncVibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated VibrationEffectProto effects = 1;
+    repeated int32 vibrator_ids = 2;
+}
+
+// A com.android.os.CombinedVibrationEffect object.
+message CombinedVibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated SyncVibrationEffectProto effects = 1;
+    repeated int32 delays = 2;
+}
+
+message VibrationAttributesProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 usage = 1;
+    optional int32 audio_usage = 2;
+    optional int32 flags = 3;
+}
+
+// Next id: 7
+message VibrationProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int64 start_time = 1;
+    optional int64 end_time = 2;
+    optional CombinedVibrationEffectProto effect = 3;
+    optional CombinedVibrationEffectProto original_effect = 4;
+    optional VibrationAttributesProto attributes = 5;
+    optional int32 status = 6;
+}
+
+// Next id: 18
+message VibratorManagerServiceDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 vibrator_ids = 1;
+    optional VibrationProto current_vibration = 2;
+    optional bool is_vibrating = 3;
+    optional VibrationProto current_external_vibration = 4;
+    optional bool vibrator_under_external_control = 5;
+    optional bool low_power_mode = 6;
+    optional int32 haptic_feedback_intensity = 7;
+    optional int32 haptic_feedback_default_intensity = 8;
+    optional int32 notification_intensity = 9;
+    optional int32 notification_default_intensity = 10;
+    optional int32 ring_intensity = 11;
+    optional int32 ring_default_intensity = 12;
+    repeated VibrationProto previous_ring_vibrations = 13;
+    repeated VibrationProto previous_notification_vibrations = 14;
+    repeated VibrationProto previous_alarm_vibrations = 15;
+    repeated VibrationProto previous_vibrations = 16;
+    repeated VibrationProto previous_external_vibrations = 17;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
deleted file mode 100644
index 9e42e9e..0000000
--- a/core/proto/android/server/vibratorservice.proto
+++ /dev/null
@@ -1,97 +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.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-option java_multiple_files = true;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-
-message OneShotProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 duration = 1;
-    repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
-   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-   repeated int32 timings = 1;
-   repeated int32 amplitudes = 2;
-   required bool repeat = 3;
-}
-
-message PrebakedProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int32 effect_id = 1;
-    optional int32 effect_strength = 2;
-    optional int32 fallback = 3;
-}
-
-message ComposedProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 effect_ids = 1;
-    repeated float effect_scales = 2;
-    repeated int32 delays = 3;
-}
-
-// A com.android.os.VibrationEffect object.
-message VibrationEffectProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional OneShotProto oneshot = 3;
-    optional WaveformProto waveform = 1;
-    optional PrebakedProto prebaked = 2;
-    optional ComposedProto composed = 4;
-}
-
-message VibrationAttributesProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int32 usage = 1;
-    optional int32 audio_usage = 2;
-    optional int32 flags = 3;
-}
-
-// Next id: 7
-message VibrationProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int64 start_time = 1;
-    optional int64 end_time = 4;
-    optional VibrationEffectProto effect = 2;
-    optional VibrationEffectProto original_effect = 3;
-    optional VibrationAttributesProto attributes = 5;
-    optional int32 status = 6;
-}
-
-// Next id: 17
-message VibratorServiceDumpProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional VibrationProto current_vibration = 1;
-    optional bool is_vibrating = 2;
-    optional VibrationProto current_external_vibration = 3;
-    optional bool vibrator_under_external_control = 4;
-    optional bool low_power_mode = 5;
-    optional int32 haptic_feedback_intensity = 6;
-    optional int32 haptic_feedback_default_intensity = 14;
-    optional int32 notification_intensity = 7;
-    optional int32 notification_default_intensity = 15;
-    optional int32 ring_intensity = 8;
-    optional int32 ring_default_intensity = 16;
-    repeated VibrationProto previous_ring_vibrations = 9;
-    repeated VibrationProto previous_notification_vibrations = 10;
-    repeated VibrationProto previous_alarm_vibrations = 11;
-    repeated VibrationProto previous_vibrations = 12;
-    repeated VibrationProto previous_external_vibrations = 13;
-}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 99014c5..ba43ee7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1870,6 +1870,12 @@
     <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
+         preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
+         <p>Not for use by third-party or privileged applications. -->
+    <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -5465,6 +5471,13 @@
     <permission android:name="android.permission.MANAGE_GAME_MODE"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+         when they are performing reboot-blocking work.
+         @hide -->
+    <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
+                android:protectionLevel="signature|privileged" />
+
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
new file mode 100644
index 0000000..513da5e
--- /dev/null
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<android.window.SplashScreenView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical">
+
+    <View android:id="@+id/splashscreen_icon_view"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:layout_gravity="center"/>
+
+    <View android:id="@+id/splashscreen_branding_view"
+          android:layout_height="wrap_content"
+          android:layout_width="wrap_content"
+          android:layout_gravity="center_horizontal|bottom"
+          android:layout_marginBottom="60dp"/>
+
+</android.window.SplashScreenView>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index aeb4fc468..927a8b4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -362,6 +362,12 @@
              surface when the app has not drawn any content into this area. One example is
              when the user is resizing a window of an activity in multi-window mode. -->
         <attr name="windowBackgroundFallback" format="reference|color" />
+        <!-- Blur the screen behind the window with the bounds of the window.
+             The radius defines the size of the neighbouring area, from which pixels will be
+             averaged to form the final color for each pixel in the region.
+             A radius of 0 means no blur. The higher the radius, the denser the blur.
+             Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. -->
+        <attr name="windowBackgroundBlurRadius" format="dimension" />
         <!-- Drawable to use as a frame around the window. -->
         <attr name="windowFrame" format="reference" />
         <!-- Flag indicating whether there should be no title on this window. -->
@@ -1966,6 +1972,7 @@
     <declare-styleable name="Window">
         <attr name="windowBackground" />
         <attr name="windowBackgroundFallback" />
+        <attr name="windowBackgroundBlurRadius" />
         <attr name="windowContentOverlay" />
         <attr name="windowFrame" />
         <attr name="windowNoTitle" />
@@ -2255,6 +2262,23 @@
             -->
             <enum name="always" value="3" />
         </attr>
+
+        <!-- The background color for the splash screen, if not specify then system will
+             calculate from windowBackground. -->
+        <attr name="windowSplashScreenBackground" format="color"/>
+
+        <!-- Replace an icon in the center of the starting window, if the object is animated
+             and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also
+             play the animation while showing the starting window. -->
+        <attr name="windowSplashScreenAnimatedIcon" format="reference"/>
+        <!-- The duration, in milliseconds, of the window splash screen icon animation duration
+             when playing the splash screen starting window. The maximum animation duration should
+             be limited below 1000ms. -->
+        <attr name="windowSplashScreenAnimationDuration" format="integer"/>
+
+        <!-- Place an drawable image in the bottom of the starting window, it can be used to
+             represent the branding of the application. -->
+        <attr name="windowSplashScreenBrandingImage" format="reference"/>
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -9253,6 +9277,7 @@
         <attr name="shortcutShortLabel" format="reference" />
         <attr name="shortcutLongLabel" format="reference" />
         <attr name="shortcutDisabledMessage" format="reference" />
+        <attr name="splashScreenTheme" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="ShortcutCategories">
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index debdab0..7066419 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -926,4 +926,12 @@
     <dimen name="controls_thumbnail_image_max_height">140dp</dimen>
     <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
     <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
+
+    <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_background_radius">16dp</dimen>
+    <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_inner_radius">8dp</dimen>
+    <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_internal_padding">16dp</dimen>
+
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 068987e..ca40564 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3065,6 +3065,12 @@
     <public name="clipToOutline" />
     <public name="edgeEffectType" />
     <public name="knownCerts" />
+    <public name="windowBackgroundBlurRadius"/>
+    <public name="windowSplashScreenBackground"/>
+    <public name="windowSplashScreenAnimatedIcon"/>
+    <public name="windowSplashScreenAnimationDuration"/>
+    <public name="windowSplashScreenBrandingImage"/>
+    <public name="splashScreenTheme" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3105,6 +3111,11 @@
 
   <public-group type="dimen" first-id="0x01050008">
     <!-- dimension definitions go here -->
+
+    <!-- System-provided dimensions for app widgets. -->
+    <public name="system_app_widget_background_radius" />
+    <public name="system_app_widget_inner_radius" />
+    <public name="system_app_widget_internal_padding" />
   </public-group>
 
   <public-group type="bool" first-id="0x01110007">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4109d4c..f66d33b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1818,6 +1818,9 @@
   <java-symbol type="string" name="forward_intent_to_owner" />
   <java-symbol type="string" name="forward_intent_to_work" />
   <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" />
+  <java-symbol type="layout" name="splash_screen_view" />
+  <java-symbol type="id" name="splashscreen_icon_view" />
+  <java-symbol type="id" name="splashscreen_branding_view" />
 
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 049ba23..87ae162 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -167,6 +167,9 @@
         <!-- Window attributes -->
         <item name="windowBackground">@drawable/screen_background_selector_dark</item>
         <item name="windowBackgroundFallback">?attr/colorBackground</item>
+        <item name="windowSplashScreenBackground">@color/transparent</item>
+        <item name="windowSplashScreenAnimatedIcon">@null</item>
+        <item name="windowSplashScreenBrandingImage">@null</item>
         <item name="windowClipToOutline">false</item>
         <item name="windowFrame">@null</item>
         <item name="windowNoTitle">false</item>
diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
new file mode 100644
index 0000000..606e81d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionInfoTest {
+    private static final String KNOWN_CERT_DIGEST_1 =
+            "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+    private static final String KNOWN_CERT_DIGEST_2 =
+            "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6";
+
+    @Test
+    public void createFromParcel_returnsKnownCerts() {
+        // The platform supports a knownSigner permission protection flag that allows one or more
+        // trusted signing certificates to be specified with the permission declaration; if a
+        // requesting app is signed by any of these trusted certificates the permission is granted.
+        // This test verifies the Set of knownCerts is properly parceled / unparceled.
+        PermissionInfo permissionInfo = new PermissionInfo();
+        permissionInfo.protectionLevel =
+                PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+        permissionInfo.knownCerts = new ArraySet<>(2);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2);
+        Parcel parcel = Parcel.obtain();
+        permissionInfo.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(unparceledPermissionInfo.knownCerts);
+        assertEquals(2, unparceledPermissionInfo.knownCerts.size());
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1));
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2));
+    }
+}
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 1a28b73..9a9b474 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,8 +2,11 @@
 per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
 
 # Haptics
+per-file CombinedVibrationEffectTest.java = michaelwr@google.com
 per-file ExternalVibrationTest.java = michaelwr@google.com
 per-file VibrationEffectTest.java = michaelwr@google.com
+per-file VibratorInfoTest.java = michaelwr@google.com
+per-file VibratorTest.java = michaelwr@google.com
 
 # Power
 per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 0fac4f7..2e6e0de 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -68,6 +68,7 @@
         SystemServicePowerCalculatorTest.class,
         UserPowerCalculatorTest.class,
         VideoPowerCalculatorTest.class,
+        WakelockPowerCalculatorTest.class,
 
         com.android.internal.power.MeasuredEnergyStatsTest.class
     })
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 0ddc4c0..5edd58f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -49,6 +49,7 @@
     };
 
     private BatteryUsageStats mBatteryUsageStats;
+    private boolean mScreenOn;
 
     public BatteryUsageStatsRule() {
         Context context = InstrumentationRegistry.getContext();
@@ -97,6 +98,11 @@
         return this;
     }
 
+    public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) {
+        mScreenOn = screenOn;
+        return this;
+    }
+
     public void setNetworkStats(NetworkStats networkStats) {
         mBatteryStats.setNetworkStats(networkStats);
     }
@@ -115,6 +121,7 @@
     private void noteOnBattery() {
         mBatteryStats.setOnBatteryInternal(true);
         mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
+        mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
     }
 
     public PowerProfile getPowerProfile() {
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 24741fe..dfbf28b 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -18,9 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Binder;
 import android.os.Process;
+import android.os.UidBatteryConsumer;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -39,34 +46,48 @@
 @RunWith(AndroidJUnit4.class)
 public class SystemServicePowerCalculatorTest {
 
-    private static final double PRECISION = 0.0000001;
+    private static final double PRECISION = 0.000001;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setNumCpuClusters(2)
+            .setNumSpeedStepsInCpuCluster(0, 2)
+            .setNumSpeedStepsInCpuCluster(1, 2)
+            .setAveragePowerForCpuCluster(0, 360)
+            .setAveragePowerForCpuCluster(1, 480)
+            .setAveragePowerForCpuCore(0, 0, 300)
+            .setAveragePowerForCpuCore(0, 1, 400)
+            .setAveragePowerForCpuCore(1, 0, 500)
+            .setAveragePowerForCpuCore(1, 1, 600);
 
+    private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
     private MockBatteryStatsImpl mMockBatteryStats;
     private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
     private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
 
     @Before
     public void setUp() throws IOException {
+        mMockUserInfoProvider = mock(BatteryStatsImpl.UserInfoProvider.class);
         mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
         mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
         mMockBatteryStats = mStatsRule.getBatteryStats()
                 .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
                 .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
-                .setUserInfoProvider(new MockUserInfoProvider());
+                .setUserInfoProvider(mMockUserInfoProvider);
     }
 
     @Test
-    public void testCalculateApp() {
-        // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
+    public void testPowerProfileBasedModel() {
+        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+        // Test Power Profile has two CPU clusters with 2 speeds each, thus 4 freq times total
         mMockSystemServerCpuThreadReader.setCpuTimes(
-                new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
-                new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
+                new long[] {30000, 40000, 50000, 60000},
+                new long[] {20000, 30000, 40000, 50000});
 
         mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
-                new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000}
+                new long[] {10000, 20000, 30000, 40000}
         );
 
         mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -101,14 +122,17 @@
         SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
                 mStatsRule.getPowerProfile());
 
-        mStatsRule.apply(calculator);
+        mStatsRule.apply(new FakeCpuPowerCalculator(), calculator);
 
         assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
-                .isWithin(PRECISION).of(0.00016269);
+                .isWithin(PRECISION).of(1.888888);
         assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
-                .isWithin(PRECISION).of(0.00146426);
+                .isWithin(PRECISION).of(17.0);
+        assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
+                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
+                .isWithin(PRECISION).of(-18.888888);
     }
 
     private static class MockKernelCpuUidFreqTimeReader extends
@@ -137,7 +161,7 @@
     }
 
     private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader {
-        private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
+        private final SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
 
         MockSystemServerCpuThreadReader() {
             super(null);
@@ -154,16 +178,15 @@
         }
     }
 
-    private static class MockUserInfoProvider extends BatteryStatsImpl.UserInfoProvider {
-        @Nullable
+    private static class FakeCpuPowerCalculator extends PowerCalculator {
         @Override
-        protected int[] getUserIds() {
-            return new int[0];
-        }
-
-        @Override
-        public boolean exists(int userId) {
-            return true;
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            if (u.getUid() == Process.SYSTEM_UID) {
+                // SystemServer must be attributed at least as much power as the total
+                // of all system services requested by apps.
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 1000000);
+            }
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
new file mode 100644
index 0000000..4f71b43
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.WorkSource;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockPowerCalculatorTest {
+    private static final double PRECISION = 0.00001;
+
+    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_PID = 3145;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
+
+    @Test
+    public void testTimerBasedModel() {
+        mStatsRule.getUidStats(Process.ROOT_UID);
+
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+                BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000);
+        batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+                BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
+
+        mStatsRule.setTime(10_000_000, 6_000_000);
+
+        WakelockPowerCalculator calculator =
+                new WakelockPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+                .isEqualTo(1000);
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.1);
+
+        UidBatteryConsumer osConsumer = mStatsRule.getUidBatteryConsumer(Process.ROOT_UID);
+        assertThat(osConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+                .isEqualTo(5000);
+        assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.5);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..b40fe24
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS
@@ -0,0 +1,3 @@
+# LockSettings related
+per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0484a9a..403dc65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -474,6 +474,8 @@
         <!-- Permission required for GTS test - GtsAssistIntentTestCases -->
         <permission name="android.permission.MANAGE_SOUND_TRIGGER" />
         <permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
+        <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+        <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 222c9bd..ded4a27 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -811,12 +811,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-1144293044": {
-      "message": "SURFACE SET FREEZE LAYER: %s",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
-    },
     "-1142279614": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
       "level": "VERBOSE",
@@ -895,12 +889,6 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
-    "-1088782910": {
-      "message": "Translucent=%s Floating=%s ShowWallpaper=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1076978367": {
       "message": "thawRotation: mRotation=%d",
       "level": "VERBOSE",
@@ -1261,12 +1249,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
-    "-639305784": {
-      "message": "Could not report config changes to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowToken.java"
-    },
     "-639217716": {
       "message": "setFocusedApp %s displayId=%d Callers=%s",
       "level": "INFO",
@@ -1417,12 +1399,6 @@
       "group": "WM_DEBUG_KEEP_SCREEN_ON",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "-477481651": {
-      "message": "SURFACE DESTROY PENDING: %s. %s",
-      "level": "INFO",
-      "group": "WM_SHOW_SURFACE_ALLOC",
-      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
-    },
     "-463348344": {
       "message": "Removing and adding activity %s to root task at top callers=%s",
       "level": "INFO",
@@ -1693,6 +1669,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-124316973": {
+      "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-118786523": {
       "message": "Resume failed; resetting state to %s: %s",
       "level": "VERBOSE",
@@ -1903,12 +1885,6 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "123161180": {
-      "message": "SEVER CHILDREN",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
-    },
     "140319294": {
       "message": "IME target changed within ActivityRecord",
       "level": "DEBUG",
@@ -2569,12 +2545,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "838570988": {
-      "message": "Could not report token removal to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowToken.java"
-    },
     "872933199": {
       "message": "Changing focus from %s to %s displayId=%d Callers=%s",
       "level": "DEBUG",
@@ -3235,6 +3205,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "1699269281": {
+      "message": "Don't organize or trigger events for untrusted displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "1720229827": {
       "message": "Creating animation bounds layer",
       "level": "INFO",
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index b70fa0e..88cf96a 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -39,6 +39,7 @@
 import android.view.NativeVectorDrawableAnimator;
 import android.view.PixelCopy;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.animation.AnimationUtils;
 
@@ -314,6 +315,16 @@
     }
 
     /**
+     * Sets the SurfaceControl to be used internally inside render thread
+     * @hide
+     * @param surfaceControl The surface control to pass to render thread in hwui.
+     *        If null, any previous references held in render thread will be discarded.
+    */
+    public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) {
+        nSetSurfaceControl(mNativeProxy, surfaceControl != null ? surfaceControl.mNativeObject : 0);
+    }
+
+    /**
      * Sets the parameters that can be used to control a render request for a
      * {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
      * than a single frame request.
@@ -1216,6 +1227,8 @@
 
     private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
 
+    private static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl);
+
     private static native boolean nPause(long nativeProxy);
 
     private static native void nSetStopped(long nativeProxy, boolean stopped);
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 40c75a4..32c777c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1346,6 +1346,19 @@
         }
     }
 
+    static {
+        // Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
+        // TODO: add new attribute to fonts.xml to preload fonts in Zygote.
+        preloadFontFile("/system/fonts/Roboto-Regular.ttf");
+    }
+
+    private static void preloadFontFile(String filePath) {
+        File file = new File(filePath);
+        if (file.exists()) {
+            nativeWarmUpCache(filePath);
+        }
+    }
+
     /** @hide */
     @VisibleForTesting
     public static void destroySystemFontMap() {
@@ -1464,4 +1477,6 @@
     private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer);
 
     private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
+
+    private static native void nativeWarmUpCache(String fileName);
 }
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index b153c99..f826b24 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -27,7 +27,6 @@
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
-import android.util.LongSparseLongArray;
 import android.util.TypedValue;
 
 import com.android.internal.annotations.GuardedBy;
@@ -63,10 +62,9 @@
             NativeAllocationRegistry.createMalloced(
                     ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont());
 
-    private static final Object SOURCE_ID_LOCK = new Object();
-    @GuardedBy("SOURCE_ID_LOCK")
-    private static final LongSparseLongArray FONT_SOURCE_ID_MAP =
-            new LongSparseLongArray(300);  // System font has 200 fonts, so 300 should be enough.
+    private static final NativeAllocationRegistry FONT_REGISTRY =
+            NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
+                    nGetReleaseNativeFont());
 
     /**
      * A builder class for creating new Font.
@@ -519,18 +517,19 @@
     private @Nullable FontVariationAxis[] mAxes = null;
     @GuardedBy("mLock")
     private @NonNull LocaleList mLocaleList = null;
-    @GuardedBy("mLock")
-    private int mSourceIdentifier = -1;
 
     /**
      * Use Builder instead
      *
      * Caller must increment underlying minikin::Font ref count.
+     * This class takes the ownership of the passing native objects.
      *
      * @hide
      */
     public Font(long nativePtr) {
         mNativePtr = nativePtr;
+
+        FONT_REGISTRY.registerNativeAllocation(this, mNativePtr);
     }
 
     /**
@@ -751,20 +750,7 @@
      * @return an unique identifier for the font source data.
      */
     public int getSourceIdentifier() {
-        synchronized (mLock) {
-            if (mSourceIdentifier == -1) {
-                long bufferAddress = nGetBufferAddress(mNativePtr);
-                synchronized (SOURCE_ID_LOCK) {
-                    long id = FONT_SOURCE_ID_MAP.get(bufferAddress, -1);
-                    if (id == -1) {
-                        id = FONT_SOURCE_ID_MAP.size();
-                        FONT_SOURCE_ID_MAP.append(bufferAddress, id);
-                    }
-                    mSourceIdentifier = (int) id;
-                }
-            }
-            return mSourceIdentifier;
-        }
+        return nGetSourceId(mNativePtr);
     }
 
     /**
@@ -883,6 +869,9 @@
     private static native long nGetBufferAddress(long font);
 
     @CriticalNative
+    private static native int nGetSourceId(long font);
+
+    @CriticalNative
     private static native long nGetReleaseNativeFont();
 
     @FastNative
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 7bd5817..d1fe2cd 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
@@ -189,6 +190,17 @@
         return TextUtils.join(",", axes);
     }
 
+    /**
+     * Stringify the array of FontVariationAxis.
+     * @hide
+     */
+    public static @NonNull String toFontVariationSettings(@Nullable List<FontVariationAxis> axes) {
+        if (axes == null) {
+            return "";
+        }
+        return TextUtils.join(",", axes);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o == this) {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index e92eaca..2b0d7e5 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -236,6 +236,47 @@
  * keyStore.load(null);
  * key = (SecretKey) keyStore.getKey("key2", null);
  * }</pre>
+ *
+ * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3>
+ * This example illustrates how to generate an elliptic curve key pair, used to establish a shared
+ * secret with another party using ECDH key agreement.
+ * <pre> {@code
+ * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+ *         KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ * keyPairGenerator.initialize(
+ *         new KeyGenParameterSpec.Builder(
+ *             "eckeypair",
+ *             KeyProperties.PURPOSE_AGREE_KEY)
+ *             .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+ *             .build());
+ * KeyPair myKeyPair = keyPairGenerator.generateKeyPair();
+ *
+ * // Exchange public keys with server. A new ephemeral key MUST be used for every message.
+ * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server.
+ *
+ * // Create a shared secret based on our private key and the other party's public key.
+ * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore");
+ * keyAgreement.init(myKeyPair.getPrivate());
+ * keyAgreement.doPhase(serverEphemeralPublicKey, true);
+ * byte[] sharedSecret = keyAgreement.generateSecret();
+ *
+ * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation
+ * // function with some other data: "salt" and "info". Salt is an optional random value,
+ * // omitted in this example. It's good practice to include both public keys and any other
+ * // key negotiation data in info. Here we use the public keys and a label that indicates
+ * // messages encrypted with this key are coming from the server.
+ * byte[] salt = {};
+ * ByteArrayOutputStream info = new ByteArrayOutputStream();
+ * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8));
+ * info.write(myKeyPair.getPublic().getEncoded());
+ * info.write(serverEphemeralPublicKey.getEncoded());
+ *
+ * // This example uses the Tink library and the HKDF key derivation function.
+ * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf(
+ *         "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32));
+ * byte[] associatedData = {};
+ * return key.decrypt(ciphertext, associatedData);
+ * }
  */
 public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
 
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 3ebca6a..293ab05 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -100,6 +100,15 @@
 
     /**
      * Purpose of key: creating a shared ECDH secret through key agreement.
+     *
+     * <p>A key having this purpose can be combined with the elliptic curve public key of another
+     * party to establish a shared secret over an insecure channel. It should be used  as a
+     * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is
+     * available <a
+     * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh"
+     * >here</a>).
+     * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this
+     * article</a> for a more detailed explanation.
      */
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
index 0bf6965..5c91cf41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -72,7 +72,7 @@
 
     private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
         List<ExtensionDisplayFeature> features = new ArrayList<>();
-        int displayId = activity.getDisplayId();
+        int displayId = activity.getDisplay().getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             Log.w(TAG, "This sample doesn't support display features on secondary displays");
             return features;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 1094a0e..d3700f8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -84,7 +84,7 @@
 
     private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
         List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
-        int displayId = activity.getDisplayId();
+        int displayId = activity.getDisplay().getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             Log.w(TAG, "This sample doesn't support display features on secondary displays");
             return features;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 96e0559..0540aee 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -71,27 +71,6 @@
       "$(locations :wm_shell-sources)",
     out: ["wm_shell_protolog.json"],
 }
-
-filegroup {
-    name: "wm_shell_protolog.json",
-    srcs: ["res/raw/wm_shell_protolog.json"],
-}
-
-genrule {
-    name: "checked-wm_shell_protolog.json",
-    srcs: [
-        ":generate-wm_shell_protolog.json",
-        ":wm_shell_protolog.json",
-    ],
-    cmd: "cp $(location :generate-wm_shell_protolog.json) $(out) && " +
-      "{ ! (diff $(out) $(location :wm_shell_protolog.json) | grep -q '^<') || " +
-      "{ echo -e '\\n\\n################################################################\\n#\\n" +
-      "#  ERROR: ProtoLog viewer config is stale.  To update it, run:\\n#\\n" +
-      "#  cp $(location :generate-wm_shell_protolog.json) " +
-      "$(location :wm_shell_protolog.json)\\n#\\n" +
-      "################################################################\\n\\n' >&2 && false; } }",
-    out: ["wm_shell_protolog.json"],
-}
 // End ProtoLog
 
 java_library {
@@ -115,6 +94,9 @@
     resource_dirs: [
         "res",
     ],
+    java_resources: [
+        ":generate-wm_shell_protolog.json"
+    ],
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
new file mode 100644
index 0000000..cd31531
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.wm.shell.sizecompatui.SizeCompatRestartButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <ImageButton
+        android:id="@+id/size_compat_restart_button"
+        android:layout_width="@dimen/size_compat_button_size"
+        android:layout_height="@dimen/size_compat_button_size"
+        android:layout_gravity="center"
+        android:src="@drawable/size_compat_restart_button"
+        android:contentDescription="@string/restart_button_description"/>
+
+</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
deleted file mode 100644
index 9c3d84e..0000000
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ /dev/null
@@ -1,286 +0,0 @@
-{
-  "version": "1.0.0",
-  "messages": {
-    "-2076257741": {
-      "message": "Transition requested: %s %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1683614271": {
-      "message": "Existing task: id=%d component=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1671119352": {
-      "message": " Delegate animation for %s to %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "-1501874464": {
-      "message": "Fullscreen Task Appeared: #%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
-    },
-    "-1382704050": {
-      "message": "Display removed: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "-1362429294": {
-      "message": "%s onTaskAppeared Primary taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "-1340279385": {
-      "message": "Remove listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1325223370": {
-      "message": "Task appeared taskId=%d listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1312360667": {
-      "message": "createRootTask() displayId=%d winMode=%d listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1308483871": {
-      "message": " try handler %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1297259344": {
-      "message": " animated by %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1269886472": {
-      "message": "Transition %s doesn't have explicit remote, search filters for match for %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "-1006733970": {
-      "message": "Display added: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "-1000962629": {
-      "message": "Animate bounds: from=%s to=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
-    },
-    "-880817403": {
-      "message": "Task vanished taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-742394458": {
-      "message": "pair task1=%d task2=%d in AppPair=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
-    },
-    "-710770147": {
-      "message": "Add target: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
-    },
-    "-298656957": {
-      "message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "-234284913": {
-      "message": "unpair taskId=%d pair=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
-    },
-    "138343607": {
-      "message": " try firstHandler %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "157713005": {
-      "message": "Task info changed taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "214412327": {
-      "message": "RemoteTransition directly requested for %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "274140888": {
-      "message": "Animate alpha: from=%d to=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
-    },
-    "325110414": {
-      "message": "Transition animations finished, notifying core %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "375908576": {
-      "message": "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "410592459": {
-      "message": "Invalid root leash (%s): %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "473543554": {
-      "message": "%s onTaskAppeared Supported",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "481673835": {
-      "message": "addListenerForTaskId taskId=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "564235578": {
-      "message": "Fullscreen Task Vanished: #%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
-    },
-    "580605218": {
-      "message": "Registering organizer",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "707170340": {
-      "message": " animated by firstHandler",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "900599280": {
-      "message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
-      "level": "ERROR",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
-    },
-    "950299522": {
-      "message": "taskId %d isn't isn't in an app-pair.",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
-    },
-    "980952660": {
-      "message": "Task root back pressed taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "982027396": {
-      "message": "%s onTaskAppeared Secondary taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "990371881": {
-      "message": " Checking filter %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "1070270131": {
-      "message": "onTransitionReady %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "1079041527": {
-      "message": "incrementPool size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "1184615936": {
-      "message": "Set drop target window visibility: displayId=%d visibility=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "1481772149": {
-      "message": "Current target: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
-    },
-    "1862198614": {
-      "message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "1891981945": {
-      "message": "release entry.taskId=%s listener=%s size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "1990759023": {
-      "message": "addListenerForType types=%s listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "2006473416": {
-      "message": "acquire entry.taskId=%s listener=%s size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "2057038970": {
-      "message": "Display changed: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    }
-  },
-  "groups": {
-    "WM_SHELL_DRAG_AND_DROP": {
-      "tag": "WindowManagerShell"
-    },
-    "WM_SHELL_TASK_ORG": {
-      "tag": "WindowManagerShell"
-    },
-    "WM_SHELL_TRANSITIONS": {
-      "tag": "WindowManagerShell"
-    }
-  }
-}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 13f1fdd..2419865 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -48,4 +48,7 @@
 
     <!-- one handed background panel default alpha -->
     <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
+
+    <!-- maximum animation duration for the icon when entering the starting window -->
+    <integer name="max_starting_window_intro_icon_anim_duration">1000</integer>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 034e65c..583964b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -173,4 +173,13 @@
 
     <!-- The width/height of the icon view on staring surface. -->
     <dimen name="starting_surface_icon_size">108dp</dimen>
+
+    <!-- The width/height of the size compat restart button. -->
+    <dimen name="size_compat_button_size">48dp</dimen>
+
+    <!-- The width of the brand image on staring surface. -->
+    <dimen name="starting_surface_brand_image_width">200dp</dimen>
+
+    <!-- The height of the brand image on staring surface. -->
+    <dimen name="starting_surface_brand_image_height">80dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index afe523a..6984ea45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -95,9 +95,16 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (!mLeashByTaskId.contains(taskId)) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mLeashByTaskId.get(taskId));
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
-        final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
         pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks");
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b22f358..efc55c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -25,6 +25,8 @@
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Binder;
@@ -38,9 +40,6 @@
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
@@ -83,6 +82,16 @@
         default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
         default void onTaskVanished(RunningTaskInfo taskInfo) {}
         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
+        /** Whether this task listener supports size compat UI. */
+        default boolean supportSizeCompatUI() {
+            // All TaskListeners should support size compat except PIP.
+            return true;
+        }
+        /** Attaches the a child window surface to the task surface. */
+        default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+            throw new IllegalStateException(
+                    "This task listener doesn't support child surface attachment.");
+        }
         default void dump(@NonNull PrintWriter pw, String prefix) {};
     }
 
@@ -111,23 +120,23 @@
     private final SizeCompatUIController mSizeCompatUI;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+                new StartingSurfaceDrawer(context, mainExecutor));
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
             SizeCompatUIController sizeCompatUI) {
-        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
+        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+                new StartingSurfaceDrawer(context, mainExecutor));
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUIController sizeCompatUI) {
+            Context context, @Nullable SizeCompatUIController sizeCompatUI,
+            StartingSurfaceDrawer startingSurfaceDrawer) {
         super(taskOrganizerController, mainExecutor);
-        // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
-        //  by a controller, that class should be create while porting
-        //  ActivityRecord#addStartingWindow to WMShell.
-        mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
         mSizeCompatUI = sizeCompatUI;
+        mStartingSurfaceDrawer = startingSurfaceDrawer;
     }
 
     @Override
@@ -254,6 +263,11 @@
     }
 
     @Override
+    public void copySplashScreenView(int taskId) {
+        mStartingSurfaceDrawer.copySplashScreenView(taskId);
+    }
+
+    @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (mLock) {
             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
@@ -354,8 +368,10 @@
             return;
         }
 
-        // The task is vanished, notify to remove size compat UI on this Task if there is any.
-        if (taskListener == null) {
+        // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
+        // on this Task if there is any.
+        if (taskListener == null || !taskListener.supportSizeCompatUI()
+                || !taskInfo.topActivityInSizeCompat) {
             mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
                     null /* taskConfig */, null /* sizeCompatActivity*/,
                     null /* taskListener */);
@@ -363,10 +379,7 @@
         }
 
         mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                taskInfo.configuration.windowConfiguration.getBounds(),
-                // null if the top activity not in size compat.
-                taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
-                taskListener);
+                taskInfo.configuration, taskInfo.topActivityToken, taskListener);
     }
 
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index bb8a973..5992447 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -301,6 +301,14 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (mTaskInfo.taskId != taskId) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mTaskLeash);
+    }
+
+    @Override
     public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index bab5140..79f9dcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -214,6 +214,19 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (getRootTaskId() == taskId) {
+            b.setParent(mRootTaskLeash);
+        } else if (getTaskId1() == taskId) {
+            b.setParent(mTaskLeash1);
+        } else if (getTaskId2() == taskId) {
+            b.setParent(mTaskLeash2);
+        } else {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 5a493c2..0552601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -323,6 +323,14 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (!mLeashByTaskId.contains(taskId)) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mLeashByTaskId.get(taskId));
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 3064af6..71331df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -66,7 +66,6 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.transition.Transitions;
@@ -585,6 +584,12 @@
     }
 
     @Override
+    public boolean supportSizeCompatUI() {
+        // PIP doesn't support size compat.
+        return false;
+    }
+
+    @Override
     public void onFixedRotationStarted(int displayId, int newRotation) {
         mNextRotation = newRotation;
         mWaitForFixedRotation = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 4f4e7da..2b0a0cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,9 +26,9 @@
 public enum ShellProtoLogGroup implements IProtoLogGroup {
     // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
     // with those in the framework ProtoLogGroup
-    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+    WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 66ecf45..552ebde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -28,6 +28,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 
 import org.json.JSONException;
@@ -109,10 +110,10 @@
         return sServiceInstance;
     }
 
-    public int startTextLogging(Context context, String[] groups, PrintWriter pw) {
-        try {
-            mViewerConfig.loadViewerConfig(
-                    context.getResources().openRawResource(R.raw.wm_shell_protolog));
+    public int startTextLogging(String[] groups, PrintWriter pw) {
+        try (InputStream is =
+                     getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
+            mViewerConfig.loadViewerConfig(is);
             return setLogging(true /* setTextLogging */, true, pw, groups);
         } catch (IOException e) {
             Log.i(TAG, "Unable to load log definitions: IOException while reading "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
index e47e1ac..9094d7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -16,101 +16,80 @@
 
 package com.android.wm.shell.sizecompatui;
 
-import android.app.ActivityClient;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.RippleDrawable;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.Gravity;
+import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.Button;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 
 /** Button to restart the size compat activity. */
-class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener,
         View.OnLongClickListener {
-    private static final String TAG = "SizeCompatRestartButton";
 
-    final WindowManager.LayoutParams mWinParams;
-    final boolean mShouldShowHint;
-    final int mDisplayId;
-    final int mPopupOffsetX;
-    final int mPopupOffsetY;
+    private SizeCompatUILayout mLayout;
+    private ImageButton mRestartButton;
+    @VisibleForTesting
+    PopupWindow mShowingHint;
+    private WindowManager.LayoutParams mWinParams;
 
-    private IBinder mLastActivityToken;
-    private PopupWindow mShowingHint;
-
-    SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+    public SizeCompatRestartButton(@NonNull Context context) {
         super(context);
-        mDisplayId = displayId;
-        mShouldShowHint = !hasShownHint;
-        final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
-        setImageDrawable(drawable);
-        setContentDescription(context.getString(R.string.restart_button_description));
+    }
 
-        final int drawableW = drawable.getIntrinsicWidth();
-        final int drawableH = drawable.getIntrinsicHeight();
-        mPopupOffsetX = drawableW / 2;
-        mPopupOffsetY = drawableH * 2;
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
 
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    void inject(SizeCompatUILayout layout) {
+        mLayout = layout;
+        mWinParams = layout.getWindowLayoutParams();
+    }
+
+    void remove() {
+        dismissHint();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mRestartButton = findViewById(R.id.size_compat_restart_button);
         final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
         final GradientDrawable mask = new GradientDrawable();
         mask.setShape(GradientDrawable.OVAL);
         mask.setColor(color);
-        setBackground(new RippleDrawable(color, null /* content */, mask));
-        setOnClickListener(this);
-        setOnLongClickListener(this);
-
-        mWinParams = new WindowManager.LayoutParams();
-        mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
-        mWinParams.width = drawableW * 2;
-        mWinParams.height = drawableH * 2;
-        mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-        mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-        mWinParams.format = PixelFormat.TRANSLUCENT;
-        mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
-                + context.getDisplayId());
-    }
-
-    void updateLastTargetActivity(IBinder activityToken) {
-        mLastActivityToken = activityToken;
-    }
-
-    /** @return {@code false} if the target display is invalid. */
-    boolean show() {
-        try {
-            getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
-        } catch (WindowManager.InvalidDisplayException e) {
-            // The target display may have been removed when the callback has just arrived.
-            Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
-            return false;
-        }
-        return true;
-    }
-
-    void remove() {
-        if (mShowingHint != null) {
-            mShowingHint.dismiss();
-        }
-        getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+        mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
+        mRestartButton.setOnClickListener(this);
+        mRestartButton.setOnLongClickListener(this);
     }
 
     @Override
     public void onClick(View v) {
-        ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+        mLayout.onRestartButtonClicked();
     }
 
     @Override
@@ -122,20 +101,26 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mShouldShowHint) {
+        if (mLayout.mShouldShowHint) {
+            mLayout.mShouldShowHint = false;
             showHint();
         }
     }
 
     @Override
+    public void setVisibility(@Visibility int visibility) {
+        if (visibility == View.GONE && mShowingHint != null) {
+            // Also dismiss the popup.
+            dismissHint();
+        }
+        super.setVisibility(visibility);
+    }
+
+    @Override
     public void setLayoutDirection(int layoutDirection) {
-        final int gravity = getGravity(layoutDirection);
+        final int gravity = SizeCompatUILayout.getGravity(layoutDirection);
         if (mWinParams.gravity != gravity) {
             mWinParams.gravity = gravity;
-            if (mShowingHint != null) {
-                mShowingHint.dismiss();
-                showHint();
-            }
             getContext().getSystemService(WindowManager.class).updateViewLayout(this,
                     mWinParams);
         }
@@ -147,8 +132,10 @@
             return;
         }
 
+        // TODO: popup is not attached to the button surface. Need to handle this differently for
+        // non-fullscreen task.
         final View popupView = LayoutInflater.from(getContext()).inflate(
-                R.layout.size_compat_mode_hint, null /* root */);
+                R.layout.size_compat_mode_hint, null);
         final PopupWindow popupWindow = new PopupWindow(popupView,
                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
         popupWindow.setWindowLayoutType(mWinParams.type);
@@ -161,12 +148,15 @@
         final Button gotItButton = popupView.findViewById(R.id.got_it);
         gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
                 null /* content */, null /* mask */));
-        gotItButton.setOnClickListener(view -> popupWindow.dismiss());
-        popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+        gotItButton.setOnClickListener(view -> dismissHint());
+        popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX,
+                mLayout.mPopupOffsetY);
     }
 
-    private static int getGravity(int layoutDirection) {
-        return Gravity.BOTTOM
-                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    void dismissHint() {
+        if (mShowingHint != null) {
+            mShowingHint.dismiss();
+            mShowingHint = null;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 48ee86c..a3880f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -18,134 +18,166 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
-import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
- * Shows a restart-activity button on Task when the foreground activity is in size compatibility
- * mode.
+ * Controls to show/update restart-activity buttons on Tasks based on whether the foreground
+ * activities are in size compatibility mode.
  */
 public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
         DisplayImeController.ImePositionProcessor {
-    private static final String TAG = "SizeCompatUI";
+    private static final String TAG = "SizeCompatUIController";
+
+    /** Whether the IME is shown on display id. */
+    private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
 
     /** The showing buttons by task id. */
-    private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+    private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
+
     /** Avoid creating display context frequently for non-default display. */
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
-    @VisibleForTesting
     private final Context mContext;
-    private final ShellExecutor mMainExecutor;
     private final DisplayController mDisplayController;
     private final DisplayImeController mImeController;
+    private final SyncTransactionQueue mSyncQueue;
 
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    @VisibleForTesting
     public SizeCompatUIController(Context context,
             DisplayController displayController,
             DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
+            SyncTransactionQueue syncQueue) {
         mContext = context;
-        mMainExecutor = mainExecutor;
         mDisplayController = displayController;
         mImeController = imeController;
+        mSyncQueue = syncQueue;
         mDisplayController.addDisplayWindowListener(this);
         mImeController.addPositionProcessor(this);
     }
 
-    public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
+    /**
+     * Called when the Task info changed. Creates and updates the restart button if there is an
+     * activity in size compat, or removes the restart button if there is no size compat activity.
+     *
+     * @param displayId display the task and activity are in.
+     * @param taskId task the activity is in.
+     * @param taskConfig task config to place the restart button with.
+     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
+     *                           top activity in this Task is not in size compat.
+     * @param taskListener listener to handle the Task Surface placement.
+     */
+    public void onSizeCompatInfoChanged(int displayId, int taskId,
+            @Nullable Configuration taskConfig, @Nullable IBinder sizeCompatActivity,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-        // TODO Draw button on Task surface
-        if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+        if (taskConfig == null || sizeCompatActivity == null || taskListener == null) {
             // Null token means the current foreground activity is not in size compatibility mode.
-            removeRestartButton(taskId);
+            removeLayout(taskId);
+        } else if (mActiveLayouts.contains(taskId)) {
+            // Button already exists, update the button layout.
+            updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener);
         } else {
-            updateRestartButton(displayId, taskId, sizeCompatActivity);
+            // Create a new restart button.
+            createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener);
         }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
         mDisplayContextCache.remove(displayId);
-        for (int i = 0; i < mActiveButtons.size(); i++) {
-            final int taskId = mActiveButtons.keyAt(i);
-            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-            if (button != null && button.mDisplayId == displayId) {
-                removeRestartButton(taskId);
-            }
+
+        // Remove all buttons on the removed display.
+        final List<Integer> toRemoveTaskIds = new ArrayList<>();
+        forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
+        for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
+            removeLayout(toRemoveTaskIds.get(i));
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
-        final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
-        for (int i = 0; i < mActiveButtons.size(); i++) {
-            final int taskId = mActiveButtons.keyAt(i);
-            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-            if (button == null || button.mDisplayId != displayId) {
-                continue;
-            }
-
-            // Hide the button when input method is showing.
-            if (button.getVisibility() != newVisibility) {
-                button.setVisibility(newVisibility);
-            }
-        }
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+        forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout));
     }
 
-    private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
-        SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
-        if (restartButton != null) {
-            restartButton.updateLastTargetActivity(activityToken);
-            return;
+    @Override
+    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+        if (isShowing) {
+            mDisplaysWithIme.add(displayId);
+        } else {
+            mDisplaysWithIme.remove(displayId);
         }
 
+        // Hide the button when input method is showing.
+        forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
+    }
+
+    private boolean isImeShowingOnDisplay(int displayId) {
+        return mDisplaysWithIme.contains(displayId);
+    }
+
+    private void createLayout(int displayId, int taskId, Configuration taskConfig,
+            IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener) {
         final Context context = getOrCreateDisplayContext(displayId);
         if (context == null) {
-            Log.i(TAG, "Cannot get context for display " + displayId);
+            Log.e(TAG, "Cannot get context for display " + displayId);
             return;
         }
 
-        restartButton = createRestartButton(context, displayId);
-        restartButton.updateLastTargetActivity(activityToken);
-        if (restartButton.show()) {
-            mActiveButtons.append(taskId, restartButton);
-        } else {
-            onDisplayRemoved(displayId);
-        }
+        final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
+                activityToken, taskListener);
+        mActiveLayouts.put(taskId, layout);
+        layout.createSizeCompatButton(isImeShowingOnDisplay(displayId));
     }
 
     @VisibleForTesting
-    SizeCompatRestartButton createRestartButton(Context context, int displayId) {
-        final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+    SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+            Configuration taskConfig, IBinder activityToken,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, context, taskConfig,
+                taskId, activityToken, taskListener, mDisplayController.getDisplayLayout(displayId),
                 mHasShownHint);
         // Only show hint for the first time.
         mHasShownHint = true;
-        return button;
+        return layout;
     }
 
-    private void removeRestartButton(int taskId) {
-        final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-        if (button != null) {
-            button.remove();
-            mActiveButtons.remove(taskId);
+    private void updateLayout(int taskId, Configuration taskConfig,
+            IBinder sizeCompatActivity,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        if (layout == null) {
+            return;
+        }
+        layout.updateSizeCompatInfo(taskConfig, sizeCompatActivity, taskListener,
+                isImeShowingOnDisplay(layout.getDisplayId()));
+    }
+
+    private void removeLayout(int taskId) {
+        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        if (layout != null) {
+            layout.release();
+            mActiveLayouts.remove(taskId);
         }
     }
 
@@ -167,4 +199,14 @@
         }
         return context;
     }
+
+    private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+        for (int i = 0; i < mActiveLayouts.size(); i++) {
+            final int taskId = mActiveLayouts.keyAt(i);
+            final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+            if (layout != null && layout.getDisplayId() == displayId) {
+                callback.accept(layout);
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
new file mode 100644
index 0000000..5924b53
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -0,0 +1,235 @@
+/*
+ * 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.sizecompatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.app.ActivityClient;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Records and handles layout of size compat UI on a task with size compat activity. Helps to
+ * calculate proper bounds when configuration or button position changes.
+ */
+class SizeCompatUILayout {
+    private static final String TAG = "SizeCompatUILayout";
+
+    private final SyncTransactionQueue mSyncQueue;
+    private Context mContext;
+    private Configuration mTaskConfig;
+    private final int mDisplayId;
+    private final int mTaskId;
+    private IBinder mActivityToken;
+    private ShellTaskOrganizer.TaskListener mTaskListener;
+    private DisplayLayout mDisplayLayout;
+    @VisibleForTesting
+    final SizeCompatUIWindowManager mWindowManager;
+
+    @VisibleForTesting
+    @Nullable
+    SizeCompatRestartButton mButton;
+    final int mButtonSize;
+    final int mPopupOffsetX;
+    final int mPopupOffsetY;
+    boolean mShouldShowHint;
+
+    SizeCompatUILayout(SyncTransactionQueue syncQueue, Context context, Configuration taskConfig,
+            int taskId, IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout, boolean hasShownHint) {
+        mSyncQueue = syncQueue;
+        mContext = context.createConfigurationContext(taskConfig);
+        mTaskConfig = taskConfig;
+        mDisplayId = mContext.getDisplayId();
+        mTaskId = taskId;
+        mActivityToken = activityToken;
+        mTaskListener = taskListener;
+        mDisplayLayout = displayLayout;
+        mShouldShowHint = !hasShownHint;
+        mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
+
+        mButtonSize =
+                mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
+        mPopupOffsetX = mButtonSize / 4;
+        mPopupOffsetY = mButtonSize;
+    }
+
+    /** Creates the button window. */
+    void createSizeCompatButton(boolean isImeShowing) {
+        if (isImeShowing || mButton != null) {
+            // When ime is showing, wait until ime is dismiss to create UI.
+            return;
+        }
+        mButton = mWindowManager.createSizeCompatUI();
+        updateSurfacePosition();
+    }
+
+    /** Releases the button window. */
+    void release() {
+        mButton.remove();
+        mButton = null;
+        mWindowManager.release();
+    }
+
+    /** Called when size compat info changed. */
+    void updateSizeCompatInfo(Configuration taskConfig, IBinder activityToken,
+            ShellTaskOrganizer.TaskListener taskListener, boolean isImeShowing) {
+        final Configuration prevTaskConfig = mTaskConfig;
+        final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+        mTaskConfig = taskConfig;
+        mActivityToken = activityToken;
+        mTaskListener = taskListener;
+
+        // Update configuration.
+        mContext = mContext.createConfigurationContext(taskConfig);
+        mWindowManager.setConfiguration(taskConfig);
+
+        if (mButton == null || prevTaskListener != taskListener) {
+            // TaskListener changed, recreate the button for new surface parent.
+            release();
+            createSizeCompatButton(isImeShowing);
+            return;
+        }
+
+        if (!taskConfig.windowConfiguration.getBounds()
+                .equals(prevTaskConfig.windowConfiguration.getBounds())) {
+            // Reposition the button surface.
+            updateSurfacePosition();
+        }
+
+        if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
+            // Update layout for RTL.
+            mButton.setLayoutDirection(taskConfig.getLayoutDirection());
+            updateSurfacePosition();
+        }
+    }
+
+    /** Called when display layout changed. */
+    void updateDisplayLayout(DisplayLayout displayLayout) {
+        if (displayLayout == mDisplayLayout) {
+            return;
+        }
+
+        final Rect prevStableBounds = new Rect();
+        final Rect curStableBounds = new Rect();
+        mDisplayLayout.getStableBounds(prevStableBounds);
+        displayLayout.getStableBounds(curStableBounds);
+        mDisplayLayout = displayLayout;
+        if (!prevStableBounds.equals(curStableBounds)) {
+            // Stable bounds changed, update button surface position.
+            updateSurfacePosition();
+        }
+    }
+
+    /** Called when IME visibility changed. */
+    void updateImeVisibility(boolean isImeShowing) {
+        if (mButton == null) {
+            // Button may not be created because ime is previous showing.
+            createSizeCompatButton(isImeShowing);
+            return;
+        }
+
+        final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
+        if (mButton.getVisibility() != newVisibility) {
+            mButton.setVisibility(newVisibility);
+        }
+    }
+
+    /** Gets the layout params for restart button. */
+    WindowManager.LayoutParams getWindowLayoutParams() {
+        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+                mButtonSize, mButtonSize,
+                TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        winParams.gravity = getGravity(getLayoutDirection());
+        winParams.token = new Binder();
+        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId());
+        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        return winParams;
+    }
+
+    /** Called when it is ready to be placed button surface button. */
+    void attachToParentSurface(SurfaceControl.Builder b) {
+        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+    }
+
+    /** Called when the restart button is clicked. */
+    void onRestartButtonClicked() {
+        ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken);
+    }
+
+    @VisibleForTesting
+    void updateSurfacePosition() {
+        if (mButton == null || mWindowManager.getSurfaceControl() == null) {
+            return;
+        }
+        // The hint popup won't be at the correct position.
+        mButton.dismissHint();
+
+        // Use stable bounds to prevent the button from overlapping with system bars.
+        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+        final Rect stableBounds = new Rect();
+        mDisplayLayout.getStableBounds(stableBounds);
+        stableBounds.intersect(taskBounds);
+
+        // Position of the button in the container coordinate.
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? stableBounds.left - taskBounds.left
+                : stableBounds.right - taskBounds.left - mButtonSize;
+        final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;
+
+        mSyncQueue.runInSync(t ->
+                t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY));
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    private int getLayoutDirection() {
+        return mContext.getResources().getConfiguration().getLayoutDirection();
+    }
+
+    static int getGravity(int layoutDirection) {
+        return Gravity.BOTTOM
+                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
new file mode 100644
index 0000000..a7ad982
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sizecompatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.WindowlessWindowManager;
+
+import com.android.wm.shell.R;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}.
+ */
+class SizeCompatUIWindowManager extends WindowlessWindowManager {
+
+    private Context mContext;
+    private final SizeCompatUILayout mLayout;
+
+    @Nullable
+    private SurfaceControlViewHost mViewHost;
+    @Nullable
+    private SurfaceControl mLeash;
+
+    SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) {
+        super(config, null /* rootSurface */, null /* hostInputToken */);
+        mContext = context;
+        mLayout = layout;
+    }
+
+    @Override
+    public void setConfiguration(Configuration configuration) {
+        super.setConfiguration(configuration);
+        mContext = mContext.createConfigurationContext(configuration);
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName("SizeCompatUILeash")
+                .setHidden(false)
+                .setCallsite("SizeCompatUIWindowManager#attachToParentSurface");
+        mLayout.attachToParentSurface(builder);
+        mLeash = builder.build();
+        b.setParent(mLeash);
+    }
+
+    /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
+    SizeCompatRestartButton createSizeCompatUI() {
+        if (mViewHost == null) {
+            mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+        }
+
+        final SizeCompatRestartButton button = (SizeCompatRestartButton)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+        button.inject(mLayout);
+        mViewHost.setView(button, mLayout.getWindowLayoutParams());
+        return button;
+    }
+
+    /** Releases the surface control and tears down the view hierarchy. */
+    void release() {
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            new SurfaceControl.Transaction().remove(mLeash).apply();
+            mLeash = null;
+        }
+    }
+
+    /**
+     * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null}
+     * if not feasible.
+     */
+    @Nullable
+    SurfaceControl getSurfaceControl() {
+        return mLeash;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6532993..10c742b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -130,6 +130,17 @@
         }
     }
 
+    @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (mRootTaskInfo.taskId == taskId) {
+            b.setParent(mRootLeash);
+        } else if (mChildrenLeashes.contains(taskId)) {
+            b.setParent(mChildrenLeashes.get(taskId));
+        } else {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+    }
+
     void setBounds(Rect bounds, WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, bounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f3f2fc3..45d5515 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -31,23 +31,21 @@
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
 import android.util.Slog;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
+import android.view.Window;
+import android.window.SplashScreenView;
 
 import com.android.internal.R;
 import com.android.internal.graphics.palette.Palette;
 import com.android.internal.graphics.palette.Quantizer;
 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
-import com.android.internal.policy.PhoneWindow;
 
 import java.util.List;
 
 /**
  * Util class to create the view for a splash screen content.
+ * @hide
  */
-class SplashscreenContentDrawer {
+public class SplashscreenContentDrawer {
     private static final String TAG = StartingSurfaceDrawer.TAG;
     private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
 
@@ -58,15 +56,24 @@
     // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
     private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
     private final Context mContext;
-    private int mIconSize;
+    private final int mMaxIconAnimationDuration;
 
-    SplashscreenContentDrawer(Context context) {
+    private int mIconSize;
+    private int mBrandingImageWidth;
+    private int mBrandingImageHeight;
+
+    SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) {
         mContext = context;
+        mMaxIconAnimationDuration = maxIconAnimationDuration;
     }
 
     private void updateDensity() {
         mIconSize = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.starting_surface_icon_size);
+        mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
+        mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
     }
 
     private int getSystemBGColor() {
@@ -83,48 +90,92 @@
         return new ColorDrawable(getSystemBGColor());
     }
 
-    View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes,
+    SplashScreenView makeSplashScreenContentView(Window win, Context context, int iconRes,
             int splashscreenContentResId) {
         updateDensity();
-        win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
         // splash screen content will be deprecated after S.
-        final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
+        final SplashScreenView ssc =
+                makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
         if (ssc != null) {
             return ssc;
         }
 
-        final TypedArray typedArray = context.obtainStyledAttributes(
-                com.android.internal.R.styleable.Window);
-        final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
-        typedArray.recycle();
+        final SplashScreenWindowAttrs attrs =
+                SplashScreenWindowAttrs.createWindowAttrs(context);
+        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
         final Drawable themeBGDrawable;
-        if (resId == 0) {
+
+        if (attrs.mWindowBgColor != 0) {
+            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
+        } else if (attrs.mWindowBgResId != 0) {
+            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+        } else {
             Slog.w(TAG, "Window background not exist!");
             themeBGDrawable = createDefaultBackgroundDrawable();
-        } else {
-            themeBGDrawable = context.getDrawable(resId);
         }
-        final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
-                : context.getPackageManager().getDefaultActivityIcon();
+        final int animationDuration;
+        final Drawable iconDrawable;
+        if (attrs.mReplaceIcon != null) {
+            iconDrawable = attrs.mReplaceIcon;
+            animationDuration = Math.max(0,
+                    Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration));
+        } else {
+            iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
+                    : context.getPackageManager().getDefaultActivityIcon();
+            animationDuration = 0;
+        }
         // TODO (b/173975965) Tracking the performance on improved splash screen.
-        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
         return builder
-                .setPhoneWindow(win)
+                .setWindow(win)
                 .setContext(context)
                 .setThemeDrawable(themeBGDrawable)
-                .setIconDrawable(iconDrawable).build();
+                .setIconDrawable(iconDrawable)
+                .setIconAnimationDuration(animationDuration)
+                .setBrandingDrawable(attrs.mBrandingImage).build();
+    }
+
+    private static class SplashScreenWindowAttrs {
+        private int mWindowBgResId = 0;
+        private int mWindowBgColor = Color.TRANSPARENT;
+        private Drawable mReplaceIcon = null;
+        private Drawable mBrandingImage = null;
+        private int mAnimationDuration = 0;
+
+        static SplashScreenWindowAttrs createWindowAttrs(Context context) {
+            final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
+            final TypedArray typedArray = context.obtainStyledAttributes(
+                    com.android.internal.R.styleable.Window);
+            attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+            attrs.mWindowBgColor = typedArray.getColor(
+                    R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+            attrs.mReplaceIcon = typedArray.getDrawable(
+                    R.styleable.Window_windowSplashScreenAnimatedIcon);
+            attrs.mAnimationDuration = typedArray.getInt(
+                    R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+            attrs.mBrandingImage = typedArray.getDrawable(
+                    R.styleable.Window_windowSplashScreenBrandingImage);
+            typedArray.recycle();
+            if (DEBUG) {
+                Slog.d(TAG, "window attributes color: "
+                        + Integer.toHexString(attrs.mWindowBgColor)
+                        + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+                        + " brandImage " + attrs.mBrandingImage);
+            }
+            return attrs;
+        }
     }
 
     private class StartingWindowViewBuilder {
-        // materials
         private Drawable mThemeBGDrawable;
         private Drawable mIconDrawable;
-        private PhoneWindow mPhoneWindow;
+        private Window mWindow;
+        private int mIconAnimationDuration;
         private Context mContext;
+        private Drawable mBrandingDrawable;
 
         // result
         private boolean mBuildComplete = false;
-        private View mCachedResult;
+        private SplashScreenView mCachedResult;
         private int mThemeColor;
         private Drawable mFinalIconDrawable;
         private float mScale = 1f;
@@ -141,8 +192,20 @@
             return this;
         }
 
-        StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
-            mPhoneWindow = window;
+        StartingWindowViewBuilder setWindow(Window window) {
+            mWindow = window;
+            mBuildComplete = false;
+            return this;
+        }
+
+        StartingWindowViewBuilder setIconAnimationDuration(int iconAnimationDuration) {
+            mIconAnimationDuration = iconAnimationDuration;
+            mBuildComplete = false;
+            return this;
+        }
+
+        StartingWindowViewBuilder setBrandingDrawable(Drawable branding) {
+            mBrandingDrawable = branding;
             mBuildComplete = false;
             return this;
         }
@@ -153,11 +216,11 @@
             return this;
         }
 
-        View build() {
+        SplashScreenView build() {
             if (mBuildComplete) {
                 return mCachedResult;
             }
-            if (mPhoneWindow == null || mContext == null) {
+            if (mWindow == null || mContext == null) {
                 Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
                 return null;
             }
@@ -173,7 +236,7 @@
                 mFinalIconDrawable = mIconDrawable;
             }
             final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
-            mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable);
+            mCachedResult = fillViewWithIcon(mWindow, mContext, iconSize, mFinalIconDrawable);
             mBuildComplete = true;
             return mCachedResult;
         }
@@ -249,25 +312,26 @@
             return true;
         }
 
-        private View fillViewWithIcon(PhoneWindow win, Context context,
+        private SplashScreenView fillViewWithIcon(Window win, Context context,
                 int iconSize, Drawable iconDrawable) {
-            final StartingSurfaceWindowView surfaceWindowView =
-                    new StartingSurfaceWindowView(context, iconSize);
-            surfaceWindowView.setBackground(new ColorDrawable(mThemeColor));
+            final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
+            builder.setIconSize(iconSize).setBackgroundColor(mThemeColor);
             if (iconDrawable != null) {
-                surfaceWindowView.setIconDrawable(iconDrawable);
+                builder.setCenterViewDrawable(iconDrawable);
             }
+            builder.setAnimationDuration(mIconAnimationDuration);
+            if (mBrandingDrawable != null) {
+                builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth,
+                        mBrandingImageHeight);
+            }
+            final SplashScreenView splashScreenView = builder.build();
             if (DEBUG) {
-                Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView);
+                Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
             }
-            win.setContentView(surfaceWindowView);
-            makeSystemUIColorsTransparent(win);
-            return surfaceWindowView;
-        }
-
-        private void makeSystemUIColorsTransparent(PhoneWindow win) {
-            win.setStatusBarColor(Color.TRANSPARENT);
-            win.setNavigationBarColor(Color.TRANSPARENT);
+            win.setContentView(splashScreenView);
+            splashScreenView.cacheRootWindow(win);
+            splashScreenView.makeSystemUIColorsTransparent();
+            return splashScreenView;
         }
     }
 
@@ -298,8 +362,8 @@
         return root < 0.1;
     }
 
-    private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx,
-            int splashscreenContentResId) {
+    private static SplashScreenView makeSplashscreenContentDrawable(Window win,
+            Context ctx, int splashscreenContentResId) {
         // doesn't support windowSplashscreenContent after S
         // TODO add an allowlist to skip some packages if needed
         final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
@@ -316,7 +380,8 @@
         if (drawable == null) {
             return null;
         }
-        View view = new View(ctx);
+        SplashScreenView view = new SplashScreenView(ctx);
+        view.setNotCopyable();
         view.setBackground(drawable);
         win.setContentView(view);
         return view;
@@ -532,34 +597,4 @@
             }
         }
     }
-
-    private static class StartingSurfaceWindowView extends FrameLayout {
-        // TODO animate the icon view
-        private final View mIconView;
-
-        StartingSurfaceWindowView(Context context, int iconSize) {
-            super(context);
-
-            final boolean emptyIcon = iconSize == 0;
-            if (emptyIcon) {
-                mIconView = null;
-            } else {
-                mIconView = new View(context);
-                FrameLayout.LayoutParams params =
-                        new FrameLayout.LayoutParams(iconSize, iconSize);
-                params.gravity = Gravity.CENTER;
-                addView(mIconView, params);
-            }
-            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        }
-
-        // TODO support animatable icon
-        void setIconDrawable(Drawable icon) {
-            if (mIconView != null) {
-                mIconView.setBackground(icon);
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index f374922..5332291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -43,6 +43,8 @@
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.SplashScreenView;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.StartingWindowInfo;
 import android.window.TaskOrganizer;
 import android.window.TaskSnapshot;
@@ -63,7 +65,6 @@
  * class to remove the starting window of the Task.
  * @hide
  */
-
 public class StartingSurfaceDrawer {
     static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
     static final boolean DEBUG_SPLASH_SCREEN = false;
@@ -81,7 +82,10 @@
         mContext = context;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mMainExecutor = mainExecutor;
-        mSplashscreenContentDrawer = new SplashscreenContentDrawer(context);
+
+        final int maxIconAnimDuration = context.getResources().getInteger(
+                com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration);
+        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration);
     }
 
     private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
@@ -193,9 +197,8 @@
     public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
         final PreferredStartingTypeHelper helper =
                 new PreferredStartingTypeHelper(windowInfo);
-        final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
         if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) {
-            addSplashScreenStartingWindow(runningTaskInfo, appToken);
+            addSplashScreenStartingWindow(windowInfo, appToken);
         } else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) {
             final TaskSnapshot snapshot = helper.mSnapshot;
             makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
@@ -203,11 +206,13 @@
         // If prefer don't show, then don't show!
     }
 
-    private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) {
+    private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+        final RunningTaskInfo taskInfo = windowInfo.taskInfo;
         final ActivityInfo activityInfo = taskInfo.topActivityInfo;
         if (activityInfo == null) {
             return;
         }
+
         final int displayId = taskInfo.displayId;
         if (activityInfo.packageName == null) {
             return;
@@ -222,11 +227,11 @@
         }
 
         Context context = mContext;
-        int theme = activityInfo.getThemeResource();
-        if (theme == 0) {
-            // replace with the default theme if the application didn't set
-            theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight;
-        }
+        // replace with the default theme if the application didn't set
+        final int theme = windowInfo.splashScreenThemeResId != 0
+                ? windowInfo.splashScreenThemeResId
+                : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+                        : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
         if (DEBUG_SPLASH_SCREEN) {
             Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
                     + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
@@ -352,9 +357,10 @@
         }
 
         params.setTitle("Splash Screen " + activityInfo.packageName);
-        final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win,
-                context, iconRes, splashscreenContentResId[0]);
-        if (contentView == null) {
+        final SplashScreenView splashScreenView =
+                mSplashscreenContentDrawer.makeSplashScreenContentView(win, context, iconRes,
+                        splashscreenContentResId[0]);
+        if (splashScreenView == null) {
             Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!");
             return;
         }
@@ -366,7 +372,7 @@
                     + activityInfo.packageName + " / " + appToken + ": " + view);
         }
         final WindowManager wm = context.getSystemService(WindowManager.class);
-        postAddWindow(taskInfo.taskId, appToken, view, wm, params);
+        postAddWindow(taskInfo.taskId, appToken, view, wm, params, splashScreenView);
     }
 
     /**
@@ -379,7 +385,7 @@
                 snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */);
         mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
         final StartingWindowRecord tView =
-                new StartingWindowRecord(null/* decorView */, surface);
+                new StartingWindowRecord(null/* decorView */, surface, null /* splashScreenView */);
         mStartingWindowRecords.put(taskId, tView);
     }
 
@@ -393,37 +399,60 @@
         removeWindowSynced(taskId);
     }
 
-    protected void postAddWindow(int taskId, IBinder appToken,
-        View view, WindowManager wm, WindowManager.LayoutParams params) {
-        boolean shouldSaveView = true;
-        try {
-            wm.addView(view, params);
-        } catch (WindowManager.BadTokenException e) {
-            // ignore
-            Slog.w(TAG, appToken + " already running, starting window not displayed. "
-                    + e.getMessage());
-            shouldSaveView = false;
-        } catch (RuntimeException e) {
-            // don't crash if something else bad happens, for example a
-            // failure loading resources because we are loading from an app
-            // on external storage that has been unmounted.
-            Slog.w(TAG, appToken + " failed creating starting window", e);
-            shouldSaveView = false;
-        } finally {
-            if (view != null && view.getParent() == null) {
-                Slog.w(TAG, "view not successfully added to wm, removing view");
-                wm.removeViewImmediate(view);
-                shouldSaveView = false;
-            }
+    /**
+     * Called when the Task wants to copy the splash screen.
+     * @param taskId
+     */
+    public void copySplashScreenView(int taskId) {
+        final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+        SplashScreenViewParcelable parcelable;
+        if (preView != null && preView.mContentView != null
+                && preView.mContentView.isCopyable()) {
+            parcelable = new SplashScreenViewParcelable(preView.mContentView);
+        } else {
+            parcelable = null;
         }
+        if (DEBUG_SPLASH_SCREEN) {
+            Slog.v(TAG, "Copying splash screen window view for task: " + taskId
+                    + " parcelable? " + parcelable);
+        }
+        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+    }
 
-        if (shouldSaveView) {
-            removeWindowSynced(taskId);
-            mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
-            final StartingWindowRecord tView =
-                    new StartingWindowRecord(view, null /* TaskSnapshotWindow */);
-            mStartingWindowRecords.put(taskId, tView);
-        }
+    protected void postAddWindow(int taskId, IBinder appToken,
+            View view, WindowManager wm, WindowManager.LayoutParams params,
+            SplashScreenView splashScreenView) {
+        mMainExecutor.execute(() -> {
+            boolean shouldSaveView = true;
+            try {
+                wm.addView(view, params);
+            } catch (WindowManager.BadTokenException e) {
+                // ignore
+                Slog.w(TAG, appToken + " already running, starting window not displayed. "
+                        + e.getMessage());
+                shouldSaveView = false;
+            } catch (RuntimeException e) {
+                // don't crash if something else bad happens, for example a
+                // failure loading resources because we are loading from an app
+                // on external storage that has been unmounted.
+                Slog.w(TAG, appToken + " failed creating starting window", e);
+                shouldSaveView = false;
+            } finally {
+                if (view != null && view.getParent() == null) {
+                    Slog.w(TAG, "view not successfully added to wm, removing view");
+                    wm.removeViewImmediate(view);
+                    shouldSaveView = false;
+                }
+            }
+            if (shouldSaveView) {
+                removeWindowSynced(taskId);
+                mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+                final StartingWindowRecord tView = new StartingWindowRecord(view,
+                        null /* TaskSnapshotWindow */, splashScreenView);
+                splashScreenView.startIntroAnimation();
+                mStartingWindowRecords.put(taskId, tView);
+            }
+        });
     }
 
     protected void removeWindowSynced(int taskId) {
@@ -459,10 +488,13 @@
     private static class StartingWindowRecord {
         private final View mDecorView;
         private final TaskSnapshotWindow mTaskSnapshotWindow;
+        private final SplashScreenView mContentView;
 
-        StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) {
+        StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow,
+                SplashScreenView splashScreenView) {
             mDecorView = decorView;
             mTaskSnapshotWindow = taskSnapshotWindow;
+            mContentView = splashScreenView;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 176b33d..a0e9f43 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -52,6 +53,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,6 +79,8 @@
     private Context mContext;
     @Mock
     private SizeCompatUIController mSizeCompatUI;
+    @Mock
+    private StartingSurfaceDrawer mStartingSurfaceDrawer;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -112,7 +116,7 @@
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
         mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
-                mSizeCompatUI));
+                mSizeCompatUI, mStartingSurfaceDrawer));
     }
 
     @Test
@@ -279,10 +283,10 @@
 
         // sizeCompatActivity is null if top activity is not in size compat.
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration.windowConfiguration.getBounds(),
-                null /* sizeCompatActivity*/ , taskListener);
+                null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
 
         // sizeCompatActivity is non-null if top activity is in size compat.
+        clearInvocations(mSizeCompatUI);
         final RunningTaskInfo taskInfo2 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
         taskInfo2.displayId = taskInfo1.displayId;
@@ -290,14 +294,12 @@
         taskInfo2.topActivityInSizeCompat = true;
         mOrganizer.onTaskInfoChanged(taskInfo2);
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration.windowConfiguration.getBounds(),
-                taskInfo1.topActivityToken,
-                taskListener);
+                taskInfo1.configuration, taskInfo1.topActivityToken, taskListener);
 
+        clearInvocations(mSizeCompatUI);
         mOrganizer.onTaskVanished(taskInfo1);
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* sizeCompatActivity*/,
-                null /* taskListener */);
+                null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
     }
 
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
new file mode 100644
index 0000000..d9086a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatRestartButton}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatRestartButtonTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatRestartButtonTest extends ShellTestCase {
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private IBinder mActivityToken;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private DisplayLayout mDisplayLayout;
+
+    private SizeCompatUILayout mLayout;
+    private SizeCompatRestartButton mButton;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final int taskId = 1;
+        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+                taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+        mButton = (SizeCompatRestartButton)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+        mButton.inject(mLayout);
+
+        spyOn(mLayout);
+        spyOn(mButton);
+        doNothing().when(mButton).showHint();
+    }
+
+    @Test
+    public void testOnClick() {
+        doNothing().when(mLayout).onRestartButtonClicked();
+
+        mButton.onClick(mButton);
+
+        verify(mLayout).onRestartButtonClicked();
+    }
+
+    @Test
+    public void testOnLongClick() {
+        verify(mButton, never()).showHint();
+
+        mButton.onLongClick(mButton);
+
+        verify(mButton).showHint();
+    }
+
+    @Test
+    public void testOnAttachedToWindow_showHint() {
+        mLayout.mShouldShowHint = false;
+        mButton.onAttachedToWindow();
+
+        verify(mButton, never()).showHint();
+
+        mLayout.mShouldShowHint = true;
+        mButton.onAttachedToWindow();
+
+        verify(mButton).showHint();
+    }
+
+    @Test
+    public void testRemove() {
+        mButton.remove();
+
+        verify(mButton).dismissHint();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 0eb64e5..806a90b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -16,23 +16,28 @@
 
 package com.android.wm.shell.sizecompatui;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,28 +55,34 @@
 @SmallTest
 public class SizeCompatUIControllerTest extends ShellTestCase {
     private static final int DISPLAY_ID = 0;
-
-    private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+    private static final int TASK_ID = 12;
 
     private SizeCompatUIController mController;
     private @Mock DisplayController mMockDisplayController;
+    private @Mock DisplayLayout mMockDisplayLayout;
     private @Mock DisplayImeController mMockImeController;
-    private @Mock SizeCompatRestartButton mMockButton;
     private @Mock IBinder mMockActivityToken;
     private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+    private @Mock SyncTransactionQueue mMockSyncQueue;
+    private @Mock SizeCompatUILayout mMockLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doReturn(true).when(mMockButton).show();
 
+        doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
+        doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
+        doReturn(TASK_ID).when(mMockLayout).getTaskId();
         mController = new SizeCompatUIController(mContext, mMockDisplayController,
-                mMockImeController, mShellMainExecutor) {
+                mMockImeController, mMockSyncQueue) {
             @Override
-            SizeCompatRestartButton createRestartButton(Context context, int displayId) {
-                return mMockButton;
+            SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+                    Configuration taskConfig, IBinder activityToken,
+                    ShellTaskOrganizer.TaskListener taskListener) {
+                return mMockLayout;
             }
         };
+        spyOn(mController);
     }
 
     @Test
@@ -82,42 +93,72 @@
 
     @Test
     public void testOnSizeCompatInfoChanged() {
-        final int taskId = 12;
-        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Configuration taskConfig = new Configuration();
 
-        // Verify that the restart button is added with non-null size compat activity.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        // Verify that the restart button is added with non-null size compat info.
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockActivityToken, mMockTaskListener);
-        mShellMainExecutor.flushAll();
 
-        verify(mMockButton).show();
-        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
+        verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
+                eq(mMockActivityToken), eq(mMockTaskListener));
 
-        // Verify that the restart button is removed with null size compat activity.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+        // Verify that the restart button is updated with non-null new size compat info.
+        final Configuration newTaskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig,
+                mMockActivityToken, mMockTaskListener);
 
-        mShellMainExecutor.flushAll();
-        verify(mMockButton).remove();
+        verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockActivityToken, mMockTaskListener,
+                false /* isImeShowing */);
+
+        // Verify that the restart button is removed with null size compat info.
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, null, mMockTaskListener);
+
+        verify(mMockLayout).release();
+    }
+
+    @Test
+    public void testOnDisplayRemoved() {
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+                mMockActivityToken, mMockTaskListener);
+
+        mController.onDisplayRemoved(DISPLAY_ID + 1);
+
+        verify(mMockLayout, never()).release();
+
+        mController.onDisplayRemoved(DISPLAY_ID);
+
+        verify(mMockLayout).release();
+    }
+
+    @Test
+    public void testOnDisplayConfigurationChanged() {
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+                mMockActivityToken, mMockTaskListener);
+
+        final Configuration newTaskConfig = new Configuration();
+        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+
+        verify(mMockLayout, never()).updateDisplayLayout(any());
+
+        mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+
+        verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
     }
 
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
-        final int taskId = 12;
-        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockActivityToken, mMockTaskListener);
-        mShellMainExecutor.flushAll();
 
-        // Verify that the restart button is hidden when IME is visible.
-        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
 
-        verify(mMockButton).setVisibility(eq(View.GONE));
+        verify(mMockLayout).updateImeVisibility(true);
 
-        // Verify that the restart button is visible when IME is hidden.
-        doReturn(View.GONE).when(mMockButton).getVisibility();
         mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
 
-        verify(mMockButton).setVisibility(eq(View.VISIBLE));
+        verify(mMockLayout).updateImeVisibility(false);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
new file mode 100644
index 0000000..236db44
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityClient;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUILayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatUILayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUILayoutTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private IBinder mActivityToken;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private DisplayLayout mDisplayLayout;
+    @Mock private SizeCompatRestartButton mButton;
+    private Configuration mTaskConfig;
+
+    private SizeCompatUILayout mLayout;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskConfig = new Configuration();
+
+        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+                TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+
+        spyOn(mLayout);
+        spyOn(mLayout.mWindowManager);
+        doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI();
+    }
+
+    @Test
+    public void testCreateSizeCompatButton() {
+        // Not create button if IME is showing.
+        mLayout.createSizeCompatButton(true /* isImeShowing */);
+
+        verify(mLayout.mWindowManager, never()).createSizeCompatUI();
+        assertNull(mLayout.mButton);
+
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        verify(mLayout.mWindowManager).createSizeCompatUI();
+        assertNotNull(mLayout.mButton);
+    }
+
+    @Test
+    public void testRelease() {
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        mLayout.release();
+
+        assertNull(mLayout.mButton);
+        verify(mButton).remove();
+        verify(mLayout.mWindowManager).release();
+    }
+
+    @Test
+    public void testUpdateSizeCompatInfo() {
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        // No diff
+        clearInvocations(mLayout);
+        mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout, never()).updateSurfacePosition();
+        verify(mLayout, never()).release();
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+
+        // Change task listener, recreate button.
+        clearInvocations(mLayout);
+        final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+                ShellTaskOrganizer.TaskListener.class);
+        mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, newTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout).release();
+        verify(mLayout).createSizeCompatButton(anyBoolean());
+
+        // Change task bounds, update position.
+        clearInvocations(mLayout);
+        final Configuration newTaskConfiguration = new Configuration();
+        newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+        mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateDisplayLayout() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+                mContext.getResources(), false, false);
+
+        mLayout.updateDisplayLayout(displayLayout1);
+        verify(mLayout).updateSurfacePosition();
+
+        // No update if the display bounds is the same.
+        clearInvocations(mLayout);
+        final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+                mContext.getResources(), false, false);
+        mLayout.updateDisplayLayout(displayLayout2);
+        verify(mLayout, never()).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateImeVisibility() {
+        // Create button if it is not created.
+        mLayout.mButton = null;
+        mLayout.updateImeVisibility(false /* isImeShowing */);
+
+        verify(mLayout).createSizeCompatButton(false /* isImeShowing */);
+
+        // Hide button if ime is shown.
+        clearInvocations(mLayout);
+        doReturn(View.VISIBLE).when(mButton).getVisibility();
+        mLayout.updateImeVisibility(true /* isImeShowing */);
+
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+        verify(mButton).setVisibility(View.GONE);
+
+        // Show button if ime is not shown.
+        doReturn(View.GONE).when(mButton).getVisibility();
+        mLayout.updateImeVisibility(false /* isImeShowing */);
+
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+        verify(mButton).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testAttachToParentSurface() {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder();
+        mLayout.attachToParentSurface(b);
+
+        verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+    }
+
+    @Test
+    public void testOnRestartButtonClicked() {
+        spyOn(ActivityClient.getInstance());
+        doNothing().when(ActivityClient.getInstance()).restartActivityProcessIfVisible(any());
+
+        mLayout.onRestartButtonClicked();
+
+        verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index c9537af..de7d6c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -40,6 +40,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
+import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,7 +80,8 @@
 
         @Override
         protected void postAddWindow(int taskId, IBinder appToken,
-                View view, WindowManager wm, WindowManager.LayoutParams params) {
+                View view, WindowManager wm, WindowManager.LayoutParams params,
+                SplashScreenView splashScreenView) {
             // listen for addView
             mAddWindowForTask = taskId;
             mViewThemeResId = view.getContext().getThemeResId();
@@ -125,7 +127,8 @@
                 createWindowInfo(taskId, android.R.style.Theme);
         mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
         waitHandlerIdle(mainLoop);
-        verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+        verify(mStartingSurfaceDrawer).postAddWindow(
+                eq(taskId), eq(mBinder), any(), any(), any(), any());
         assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
 
         mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId);
@@ -142,7 +145,8 @@
                 createWindowInfo(taskId, 0);
         mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
         waitHandlerIdle(mainLoop);
-        verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+        verify(mStartingSurfaceDrawer).postAddWindow(
+                eq(taskId), eq(mBinder), any(), any(), any(), any());
         assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
     }
 
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 0e338f3..2db3ace 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,10 +30,11 @@
 
 namespace android {
 
-MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
-                                 std::string_view filePath, int ttcIndex,
+MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
+                                 size_t fontSize, std::string_view filePath, int ttcIndex,
                                  const std::vector<minikin::FontVariation>& axes)
         : mTypeface(std::move(typeface))
+        , mSourceId(sourceId)
         , mFontData(fontData)
         , mFontSize(fontSize)
         , mTtcIndex(ttcIndex)
@@ -141,8 +142,8 @@
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
 
-    return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath,
-                                             ttcIndex, variations);
+    return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
+                                             mFilePath, ttcIndex, variations);
 }
 
 // hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 77a2142..de9a5c2 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -30,7 +30,7 @@
 
 class ANDROID_API MinikinFontSkia : public minikin::MinikinFont {
 public:
-    MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
+    MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
                     std::string_view filePath, int ttcIndex,
                     const std::vector<minikin::FontVariation>& axes);
 
@@ -62,6 +62,7 @@
     const std::vector<minikin::FontVariation>& GetAxes() const;
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
             const std::vector<minikin::FontVariation>&) const;
+    int GetSourceId() const override { return mSourceId; }
 
     static uint32_t packFontFlags(const SkFont&);
     static void unpackFontFlags(SkFont*, uint32_t fontFlags);
@@ -73,6 +74,7 @@
 private:
     sk_sp<SkTypeface> mTypeface;
 
+    int mSourceId;
     // A raw pointer to the font data - it should be owned by some other object with
     // lifetime at least as long as this object.
     const void* mFontData;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 03f1d62..5a9d250 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -185,9 +185,9 @@
     sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
-    std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
-            std::move(typeface), data, st.st_size, kRobotoFont, 0,
-            std::vector<minikin::FontVariation>());
+    std::shared_ptr<minikin::MinikinFont> font =
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
+                                              0, std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 2e85840..ce5ac38 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -17,15 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "Minikin"
 
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include "FontUtils.h"
+#include "GraphicsJNI.h"
 #include "SkData.h"
 #include "SkFontMgr.h"
 #include "SkRefCnt.h"
 #include "SkTypeface.h"
-#include "GraphicsJNI.h"
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
 #include "Utils.h"
-#include "FontUtils.h"
+#include "fonts/Font.h"
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
@@ -35,6 +36,12 @@
 
 #include <memory>
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// The following JNI methods are kept only for compatibility reasons due to hidden API accesses.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 namespace android {
 
 struct NativeFamilyBuilder {
@@ -125,8 +132,8 @@
         return false;
     }
     std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, "", ttcIndex,
-                    builder->axes);
+            std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
+                                              fontSize, "", ttcIndex, builder->axes);
     minikin::Font::Builder fontBuilder(minikinFont);
 
     if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index fa1752c..a48d7f7 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -64,8 +64,8 @@
     sk_sp<SkImage> image = android::bitmap::toBitmap(bitmapHandle).makeImage();
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
-    sk_sp<SkImageFilter> bitmapFilter =
-        SkImageFilters::Image(image, srcRect, dstRect, kLow_SkFilterQuality);
+    sk_sp<SkImageFilter> bitmapFilter = SkImageFilters::Image(
+            image, srcRect, dstRect, SkSamplingOptions(SkFilterMode::kLinear));
     return reinterpret_cast<jlong>(bitmapFilter.release());
 }
 
@@ -150,4 +150,4 @@
     android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
             gRenderEffectMethods, NELEM(gRenderEffectMethods));
     return 0;
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 1842356..251323d 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -367,6 +367,12 @@
     return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
 }
 
+// Regular JNI
+static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
+    ScopedUtfChars filePath(env, jFilePath);
+    makeSkDataCached(filePath.c_str(), false /* fs verity */);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
@@ -390,6 +396,7 @@
          (void*)Typeface_forceSetStaticFinalField},
         {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
         {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
+        {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4966bfa..df66981 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -189,6 +189,13 @@
     }
 }
 
+static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong surfaceControlPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr);
+    proxy->setSurfaceControl(surfaceControl);
+}
+
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
         jlong proxyPtr) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -671,6 +678,8 @@
         {"nSetName", "(JLjava/lang/String;)V", (void*)android_view_ThreadedRenderer_setName},
         {"nSetSurface", "(JLandroid/view/Surface;Z)V",
          (void*)android_view_ThreadedRenderer_setSurface},
+        {"nSetSurfaceControl", "(JJ)V",
+         (void*)android_view_ThreadedRenderer_setSurfaceControl},
         {"nPause", "(J)Z", (void*)android_view_ThreadedRenderer_pause},
         {"nSetStopped", "(JZ)V", (void*)android_view_ThreadedRenderer_setStopped},
         {"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha},
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index c8471a9..5a972f5 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -137,12 +137,9 @@
     sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args);
 
     std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
-        std::move(newTypeface),
-        minikinSkia->GetFontData(),
-        minikinSkia->GetFontSize(),
-        minikinSkia->getFilePath(),
-        minikinSkia->GetFontIndex(),
-        builder->axes);
+            std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
+            minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
+            builder->axes);
     std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
               .setWeight(weight)
               .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -279,6 +276,12 @@
     return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
 }
 
+// Critical Native
+static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
+    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
+    return font->font->typeface()->GetSourceId();
+}
+
 // Fast Native
 static jlong FontFileUtil_getFontRevision(JNIEnv* env, jobject, jobject buffer, jint index) {
     NPE_CHECK_RETURN_ZERO(env, buffer);
@@ -369,6 +372,7 @@
         {"nGetIndex", "(J)I", (void*)Font_getIndex},
         {"nGetAxisCount", "(J)I", (void*)Font_getAxisCount},
         {"nGetAxisInfo", "(JI)J", (void*)Font_getAxisInfo},
+        {"nGetSourceId", "(J)I", (void*)Font_getSourceId},
 };
 
 static const JNINativeMethod gFontFileUtilMethods[] = {
@@ -409,10 +413,15 @@
     if (face == nullptr) {
         return nullptr;
     }
-    return std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize,
+    return std::make_shared<MinikinFontSkia>(std::move(face), getNewSourceId(), fontPtr, fontSize,
                                              fontPath, ttcIndex, axes);
 }
 
+int getNewSourceId() {
+    static std::atomic<int> sSourceId = {0};
+    return sSourceId++;
+}
+
 }  // namespace fonts
 
 }  // namespace android
diff --git a/libs/hwui/jni/fonts/Font.h b/libs/hwui/jni/fonts/Font.h
index b5d20bf..4bf60ee 100644
--- a/libs/hwui/jni/fonts/Font.h
+++ b/libs/hwui/jni/fonts/Font.h
@@ -33,6 +33,8 @@
         sk_sp<SkData>&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize,
         int ttcIndex, const std::vector<minikin::FontVariation>& axes);
 
+int getNewSourceId();
+
 } // namespace fonts
 
 } // namespace android
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 65afcc3..9543d47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -133,6 +133,7 @@
 void CanvasContext::destroy() {
     stopDrawing();
     setSurface(nullptr);
+    setSurfaceControl(nullptr);
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
@@ -173,6 +174,19 @@
     setupPipelineSurface();
 }
 
+void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    if (surfaceControl == mSurfaceControl) return;
+
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (mSurfaceControl != nullptr) {
+        funcs.releaseFunc(mSurfaceControl);
+    }
+    mSurfaceControl = surfaceControl;
+    if (mSurfaceControl != nullptr) {
+        funcs.acquireFunc(mSurfaceControl);
+    }
+}
+
 void CanvasContext::setupPipelineSurface() {
     bool hasSurface = mRenderPipeline->setSurface(
             mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b31883b..917b00c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -112,6 +112,7 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
     bool hasSurface() const { return mNativeSurface.get(); }
@@ -218,6 +219,9 @@
 
     RenderThread& mRenderThread;
     std::unique_ptr<ReliableSurface> mNativeSurface;
+    // The SurfaceControl reference is passed from ViewRootImpl, can be set to
+    // NULL to remove the reference
+    ASurfaceControl* mSurfaceControl = nullptr;
     // stopped indicates the CanvasContext will reject actual redraw operations,
     // and defer repaint until it is un-stopped
     bool mStopped = false;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0ade8dd..e14842f 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -84,6 +84,19 @@
     });
 }
 
+void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (surfaceControl) {
+        funcs.acquireFunc(surfaceControl);
+    }
+    mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable {
+        mContext->setSurfaceControl(control);
+        if (control) {
+            funcs.releaseFunc(control);
+        }
+    });
+}
+
 void RenderProxy::allocateBuffers() {
     mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a4adb16..366d6b5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -20,6 +20,7 @@
 #include <SkBitmap.h>
 #include <android/native_window.h>
 #include <cutils/compiler.h>
+#include <android/surface_control.h>
 #include <utils/Functor.h>
 
 #include "../FrameMetricsObserver.h"
@@ -72,6 +73,7 @@
     void setName(const char* name);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     void allocateBuffers();
     bool pause();
     void setStopped(bool stopped);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7750a31..2610186 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -33,6 +33,7 @@
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
+#include <dlfcn.h>
 #include <sys/resource.h>
 #include <utils/Condition.h>
 #include <utils/Log.h>
@@ -49,6 +50,17 @@
 
 static JVMAttachHook gOnStartHook = nullptr;
 
+ASurfaceControlFunctions::ASurfaceControlFunctions() {
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire");
+    LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_acquire!");
+
+    releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
+    LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_release!");
+}
+
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
     int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4fbb0716..bb7c518 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -78,6 +78,16 @@
     virtual ~VsyncSource() {}
 };
 
+typedef void (*ASC_acquire)(ASurfaceControl* control);
+typedef void (*ASC_release)(ASurfaceControl* control);
+
+struct ASurfaceControlFunctions {
+    ASurfaceControlFunctions();
+
+    ASC_acquire acquireFunc;
+    ASC_release releaseFunc;
+};
+
 class ChoreographerSource;
 class DummyVsyncSource;
 
@@ -121,6 +131,10 @@
 
     void preload();
 
+    const ASurfaceControlFunctions& getASurfaceControlFunctions() {
+        return mASurfaceControlFunctions;
+    }
+
     /**
      * isCurrent provides a way to query, if the caller is running on
      * the render thread.
@@ -189,6 +203,8 @@
     sk_sp<GrDirectContext> mGrContext;
     CacheManager* mCacheManager;
     sp<VulkanManager> mVkManager;
+
+    ASurfaceControlFunctions mASurfaceControlFunctions;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 5d2aa2f..ab23448 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
     sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font =
-            std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0,
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
                                               std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index a7e9a0d..5e2e559 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -569,7 +569,12 @@
 
     /** @hide */
     public long getElapsedRealtimeAgeMillis() {
-        return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+        return getElapsedRealtimeAgeMillis(SystemClock.elapsedRealtime());
+    }
+
+    /** @hide */
+    public long getElapsedRealtimeAgeMillis(long referenceRealtimeMs) {
+        return referenceRealtimeMs - NANOSECONDS.toMillis(mElapsedRealtimeNanos);
     }
 
     /**
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index cf2f0f0..a7ed091 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -10,7 +10,7 @@
           "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
         },
         {
-          "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
+          "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
         }
       ]
     }
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 92db946..44f8385 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -23,6 +23,7 @@
 import android.graphics.ImageFormat.Format;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.hardware.HardwareBuffer;
 import android.os.Handler;
@@ -202,6 +203,25 @@
         if (format == ImageFormat.UNKNOWN) {
             format = SurfaceUtils.getSurfaceFormat(surface);
         }
+        // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native
+        // allocation estimation sequence depends on the public formats values. To avoid
+        // possible errors, convert where necessary.
+        if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) {
+            int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface);
+            switch (surfaceDataspace) {
+                case StreamConfigurationMap.HAL_DATASPACE_DEPTH:
+                    format = ImageFormat.DEPTH_POINT_CLOUD;
+                    break;
+                case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH:
+                    format = ImageFormat.DEPTH_JPEG;
+                    break;
+                case StreamConfigurationMap.HAL_DATASPACE_HEIF:
+                    format = ImageFormat.HEIC;
+                    break;
+                default:
+                    format = ImageFormat.JPEG;
+            }
+        }
         // Estimate the native buffer allocation size and register it so it gets accounted for
         // during GC. Note that this doesn't include the buffers required by the buffer queue
         // itself and the buffers requested by the producer.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 1a49b85..67f1660 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1154,6 +1153,22 @@
     public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
 
     /**
+     * An optional key describing the opto-electronic transfer function
+     * requested for the output video content.
+     *
+     * The associated value is an integer: 0 if unspecified, or one of the
+     * COLOR_TRANSFER_ values. When unspecified the component will not touch the
+     * video content; otherwise the component will tone-map the raw video frame
+     * to match the requested transfer function.
+     *
+     * After configure, component's input format will contain this key to note
+     * whether the request is supported or not. If the value in the input format
+     * is the same as the requested value, the request is supported. The value
+     * is set to 0 if unsupported.
+     */
+    public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
+
+    /**
      * A key describing a unique ID for the content of a media track.
      *
      * <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings
diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp
index 748d458..359ef36 100644
--- a/media/jni/tuner/DemuxClient.cpp
+++ b/media/jni/tuner/DemuxClient.cpp
@@ -216,14 +216,13 @@
 Result DemuxClient::close() {
     if (mTunerDemux != NULL) {
         Status s = mTunerDemux->close();
+        mDemux = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDemux != NULL) {
         Result res = mDemux->close();
-        if (res == Result::SUCCESS) {
-            mDemux = NULL;
-        }
+        mDemux = NULL;
         return res;
     }
 
diff --git a/media/jni/tuner/DescramblerClient.cpp b/media/jni/tuner/DescramblerClient.cpp
index c9bacda..07be5cf 100644
--- a/media/jni/tuner/DescramblerClient.cpp
+++ b/media/jni/tuner/DescramblerClient.cpp
@@ -101,14 +101,18 @@
 Result DescramblerClient::close() {
     if (mTunerDescrambler != NULL) {
         Status s = mTunerDescrambler->close();
+        mTunerDescrambler = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDescrambler != NULL) {
-        return mDescrambler->close();
+        Result res = mDescrambler->close();
+        mDescrambler = NULL;
+        return res;
     }
 
-    return Result::INVALID_STATE;}
+    return Result::INVALID_STATE;
+}
 
 /////////////// DescramblerClient Helper Methods ///////////////////////
 
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 0400485..7793180 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -316,14 +316,13 @@
 Result DvrClient::close() {
     if (mTunerDvr != NULL) {
         Status s = mTunerDvr->close();
+        mTunerDvr = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDvr != NULL) {
         Result res = mDvr->close();
-        if (res == Result::SUCCESS) {
-            mDvr = NULL;
-        }
+        mDvr = NULL;
         return res;
     }
 
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index f618890..f31d465 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -262,14 +262,14 @@
     if (mTunerFilter != NULL) {
         Status s = mTunerFilter->close();
         closeAvSharedMemory();
+        mTunerFilter = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mFilter != NULL) {
         Result res = mFilter->close();
-        if (res == Result::SUCCESS) {
-            mFilter = NULL;
-        }
+        mFilter = NULL;
+        mFilter_1_1 = NULL;
         closeAvSharedMemory();
         return res;
     }
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 0613223..9e36642 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -322,15 +322,14 @@
 Result FrontendClient::close() {
     if (mTunerFrontend != NULL) {
         Status s = mTunerFrontend->close();
+        mTunerFrontend = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mFrontend != NULL) {
         Result result = mFrontend->close();
-        if (result == Result::SUCCESS) {
-            mFrontend = NULL;
-            mFrontend_1_1 = NULL;
-        }
+        mFrontend = NULL;
+        mFrontend_1_1 = NULL;
         return result;
     }
 
diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp
index 8d08c25..5b6e46e 100644
--- a/media/jni/tuner/LnbClient.cpp
+++ b/media/jni/tuner/LnbClient.cpp
@@ -113,11 +113,14 @@
 Result LnbClient::close() {
     if (mTunerLnb != NULL) {
         Status s = mTunerLnb->close();
+        mTunerLnb = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mLnb != NULL) {
-        return mLnb->close();
+        Result res = mLnb->close();
+        mLnb = NULL;
+        return res;
     }
 
     return Result::INVALID_STATE;
diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp
index 432238d..e123c9f 100644
--- a/media/jni/tuner/TimeFilterClient.cpp
+++ b/media/jni/tuner/TimeFilterClient.cpp
@@ -126,11 +126,14 @@
 Result TimeFilterClient::close() {
     if (mTunerTimeFilter != NULL) {
         Status s = mTunerTimeFilter->close();
+        mTunerTimeFilter = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mTimeFilter != NULL) {
-        return mTimeFilter->close();
+        Result res = mTimeFilter->close();
+        mTimeFilter = NULL;
+        return res;
     }
 
     return Result::INVALID_STATE;
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
index 9afa5d1..92a792b 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -49,17 +49,6 @@
                 }
         );
 
-        // 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,
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index fbe15bb..92d7bf0 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -824,6 +824,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
+
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
      * case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1069,106 +1070,55 @@
     }
 
     /**
-     * Checks if a VPN app supports always-on mode.
-     *
-     * In order to support the always-on feature, an app has to
-     * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
-     * </ul>
-     *
-     * @param userId The identifier of the user for whom the VPN app is installed.
-     * @param vpnPackage The canonical package name of the VPN app.
-     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+     * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
+    @Deprecated
     public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
-        try {
-            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage);
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
-     *
-     * <p>The designated package should declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
-     *
-     * @param userId The identifier of the user to set an always-on VPN for.
-     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
-     *                   to remove an existing always-on VPN configuration.
-     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
-     *        {@code false} otherwise.
-     * @param lockdownAllowlist The list of packages that are allowed to access network directly
-     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
-     *         this method must be called when a package that should be allowed is installed or
-     *         uninstalled.
-     * @return {@code true} if the package is set as always-on VPN controller;
-     *         {@code false} otherwise.
+    * Calls VpnManager#setAlwaysOnVpnPackageForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    @Deprecated
     public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
             boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
-        try {
-            return mService.setAlwaysOnVpnPackage(
-                    userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled,
+                lockdownAllowlist);
     }
 
-   /**
-     * Returns the package name of the currently set always-on VPN application.
-     * If there is no always-on VPN set, or the VPN is provided by the system instead
-     * of by an app, {@code null} will be returned.
-     *
-     * @return Package name of VPN controller responsible for always-on VPN,
-     *         or {@code null} if none is set.
+    /**
+     * Calls VpnManager#getAlwaysOnVpnPackageForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    @Deprecated
     public String getAlwaysOnVpnPackageForUser(int userId) {
-        try {
-            return mService.getAlwaysOnVpnPackage(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().getAlwaysOnVpnPackageForUser(userId);
     }
 
     /**
-     * @return whether always-on VPN is in lockdown mode.
-     *
+     * Calls VpnManager#isVpnLockdownEnabled.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+     */
+    @Deprecated
     public boolean isVpnLockdownEnabled(int userId) {
-        try {
-            return mService.isVpnLockdownEnabled(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
+        return getVpnManager().isVpnLockdownEnabled(userId);
     }
 
     /**
-     * @return the list of packages that are allowed to access network when always-on VPN is in
-     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
-     *
+     * Calls VpnManager#getVpnLockdownAllowlist.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        try {
-            return mService.getVpnLockdownWhitelist(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+     */
+    @Deprecated
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        return getVpnManager().getVpnLockdownAllowlist(userId);
     }
 
     /**
@@ -1221,6 +1171,45 @@
     }
 
     /**
+     * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
+     * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
+     * but is still supported for backwards compatibility.
+     * <p>
+     * This type of VPN is assumed always to use the system default network, and must always declare
+     * exactly one underlying network, which is the network that was the default when the VPN
+     * connected.
+     * <p>
+     * Calling this method with {@code true} enables legacy behaviour, specifically:
+     * <ul>
+     *     <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated
+     *     {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the
+     *     {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN
+     *     connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network
+     *     underlying the VPN.</li>
+     *     <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state
+     *     similarly replaced by the VPN network state.</li>
+     *     <li>Information on current network interfaces passed to NetworkStatsService will not
+     *     include any VPN interfaces.</li>
+     * </ul>
+     *
+     * @param enabled whether legacy lockdown VPN is enabled or disabled
+     *
+     * TODO: @SystemApi(client = MODULE_LIBRARIES)
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
+        try {
+            mService.setLegacyLockdownVpnEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
@@ -3180,20 +3169,13 @@
     }
 
     /**
-     * If the LockdownVpn mechanism is enabled, updates the vpn
-     * with a reload of its profile.
-     *
-     * @return a boolean with {@code} indicating success
-     *
-     * <p>This method can only be called by the system UID
-     * {@hide}
+     * Calls VpnManager#updateLockdownVpn.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
+     * @hide
      */
+    @Deprecated
     public boolean updateLockdownVpn() {
-        try {
-            return mService.updateLockdownVpn();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().updateLockdownVpn();
     }
 
     /**
@@ -4557,6 +4539,8 @@
         try {
             mService.factoryReset();
             mTetheringManager.stopAllTethering();
+            // TODO: Migrate callers to VpnManager#factoryReset.
+            getVpnManager().factoryReset();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4850,9 +4834,13 @@
         return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
     }
 
-    /** @hide */
-    public VpnManager createVpnManager() {
-        return new VpnManager(mContext, mService);
+    /**
+     * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a
+     * private final mVpnManager because ConnectivityManager is initialized before VpnManager.
+     * @hide TODO: remove.
+     */
+    public VpnManager getVpnManager() {
+        return mContext.getSystemService(VpnManager.class);
     }
 
     /** @hide */
@@ -4886,15 +4874,6 @@
         }
     }
 
-    private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        try {
-            mService.setOemNetworkPreference(preference);
-        } catch (RemoteException e) {
-            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     @NonNull
     private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
 
@@ -5096,4 +5075,60 @@
         sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
                 TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler));
     }
+
+    /**
+     * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor,
+     * OnSetOemNetworkPreferenceListener)}.
+     * @hide
+     */
+    @SystemApi
+    public interface OnSetOemNetworkPreferenceListener {
+        /**
+         * Called when setOemNetworkPreference() successfully completes.
+         */
+        void onComplete();
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param executor the executor on which listener will be invoked.
+     * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to
+     *                  communicate completion of setOemNetworkPreference(). This will only be
+     *                  called once upon successful completion of setOemNetworkPreference().
+     * @throws IllegalArgumentException if {@code preference} contains invalid preference values.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws UnsupportedOperationException if called on a non-automotive device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE)
+    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference,
+            @Nullable @CallbackExecutor final Executor executor,
+            @Nullable final OnSetOemNetworkPreferenceListener listener) {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (null != listener) {
+            Objects.requireNonNull(executor, "Executor must be non-null");
+        }
+        final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null :
+                new IOnSetOemNetworkPreferenceListener.Stub() {
+                    @Override
+                    public void onComplete() {
+                        executor.execute(listener::onComplete);
+                    }
+        };
+
+        try {
+            mService.setOemNetworkPreference(preference, listenerInternal);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index f909d13..6391802 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -20,6 +20,7 @@
 import android.net.ConnectionInfo;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.IConnectivityDiagnosticsCallback;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.LinkProperties;
@@ -42,9 +43,6 @@
 import android.os.ResultReceiver;
 
 import com.android.connectivity.aidl.INetworkAgent;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
 
 /**
  * Interface that answers queries about, and allows changing, the
@@ -122,35 +120,8 @@
 
     ProxyInfo getProxyForNetwork(in Network nework);
 
-    boolean prepareVpn(String oldPackage, String newPackage, int userId);
-
-    void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
-
-    ParcelFileDescriptor establishVpn(in VpnConfig config);
-
-    boolean provisionVpnProfile(in VpnProfile profile, String packageName);
-
-    void deleteVpnProfile(String packageName);
-
-    void startVpnProfile(String packageName);
-
-    void stopVpnProfile(String packageName);
-
-    VpnConfig getVpnConfig(int userId);
-
-    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    void startLegacyVpn(in VpnProfile profile);
-
-    LegacyVpnInfo getLegacyVpnInfo(int userId);
-
-    boolean updateLockdownVpn();
-    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
-    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
-            in List<String> lockdownWhitelist);
-    String getAlwaysOnVpnPackage(int userId);
-    boolean isVpnLockdownEnabled(int userId);
-    List<String> getVpnLockdownWhitelist(int userId);
     void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
+    void setLegacyLockdownVpnEnabled(boolean enabled);
 
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
@@ -199,10 +170,6 @@
 
     int getRestoreDefaultNetworkDelay(int networkType);
 
-    boolean addVpnAddress(String address, int prefixLength);
-    boolean removeVpnAddress(String address, int prefixLength);
-    boolean setUnderlyingNetworksForVpn(in Network[] networks);
-
     void factoryReset();
 
     void startNattKeepalive(in Network network, int intervalSeconds,
@@ -222,8 +189,6 @@
     byte[] getNetworkWatchlistConfigHash();
 
     int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
-    boolean isCallerCurrentAlwaysOnVpnApp();
-    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 
     void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
             in NetworkRequest request, String callingPackageName);
@@ -245,5 +210,6 @@
     void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
     void unregisterQosCallback(in IQosCallback callback);
 
-    void setOemNetworkPreference(in OemNetworkPreferences preference);
+    void setOemNetworkPreference(in OemNetworkPreferences preference,
+            in IOnSetOemNetworkPreferenceListener listener);
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 9d67f0b..26d14cb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2085,9 +2085,10 @@
     /**
      * Check if private dns is broken.
      *
-     * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+     * @return {@code true} if private DNS is broken on this network.
      * @hide
      */
+    @SystemApi
     public boolean isPrivateDnsBroken() {
         return mPrivateDnsBroken;
     }
@@ -2330,6 +2331,17 @@
         }
 
         /**
+         * Completely clears the contents of this object, removing even the capabilities that are
+         * set by default when the object is constructed.
+         * @return this builder
+         */
+        @NonNull
+        public Builder clearAll() {
+            mCaps.clearAll();
+            return this;
+        }
+
+        /**
          * Sets the owner UID.
          *
          * The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index b4a651c..4e3085f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -16,22 +16,6 @@
 
 package android.net;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -46,8 +30,6 @@
 import android.text.TextUtils;
 import android.util.proto.ProtoOutputStream;
 
-import java.util.Arrays;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -172,30 +154,8 @@
      * needed in terms of {@link NetworkCapabilities} features
      */
     public static class Builder {
-        /**
-         * Capabilities that are currently compatible with VCN networks.
-         */
-        private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList(
-                NET_CAPABILITY_CAPTIVE_PORTAL,
-                NET_CAPABILITY_DUN,
-                NET_CAPABILITY_FOREGROUND,
-                NET_CAPABILITY_INTERNET,
-                NET_CAPABILITY_NOT_CONGESTED,
-                NET_CAPABILITY_NOT_METERED,
-                NET_CAPABILITY_NOT_RESTRICTED,
-                NET_CAPABILITY_NOT_ROAMING,
-                NET_CAPABILITY_NOT_SUSPENDED,
-                NET_CAPABILITY_NOT_VPN,
-                NET_CAPABILITY_PARTIAL_CONNECTIVITY,
-                NET_CAPABILITY_TEMPORARILY_NOT_METERED,
-                NET_CAPABILITY_TRUSTED,
-                NET_CAPABILITY_VALIDATED);
-
         private final NetworkCapabilities mNetworkCapabilities;
 
-        // A boolean that represents the user modified NOT_VCN_MANAGED capability.
-        private boolean mModifiedNotVcnManaged = false;
-
         /**
          * Default constructor for Builder.
          */
@@ -217,7 +177,6 @@
             // maybeMarkCapabilitiesRestricted() doesn't add back.
             final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
             nc.maybeMarkCapabilitiesRestricted();
-            deduceNotVcnManagedCapability(nc);
             return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
                     ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
         }
@@ -234,9 +193,6 @@
          */
         public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
-            if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
-            }
             return this;
         }
 
@@ -248,9 +204,6 @@
          */
         public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
-            if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
-            }
             return this;
         }
 
@@ -308,9 +261,6 @@
         @NonNull
         public Builder clearCapabilities() {
             mNetworkCapabilities.clearAll();
-            // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
-            // should not be add back later.
-            mModifiedNotVcnManaged = true;
             return this;
         }
 
@@ -430,25 +380,6 @@
             mNetworkCapabilities.setSignalStrength(signalStrength);
             return this;
         }
-
-        /**
-         * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities
-         * and user intention, which includes:
-         *   1. For the requests that don't have anything besides
-         *      {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
-         *      allow the callers automatically utilize VCN networks if available.
-         *   2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
-         *      do not alter them to allow user fire request that suits their need.
-         *
-         * @hide
-         */
-        private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
-            if (mModifiedNotVcnManaged) return;
-            for (final int cap : nc.getCapabilities()) {
-                if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
-            }
-            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        }
     }
 
     // implement the Parcelable interface
diff --git a/core/java/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
similarity index 88%
rename from core/java/android/net/VpnTransportInfo.java
rename to packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 082fa58..0242ba0 100644
--- a/core/java/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -16,8 +16,10 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -26,7 +28,15 @@
 
 import java.util.Objects;
 
-/** @hide */
+/**
+ * Container for VPN-specific transport information.
+ *
+ * @see android.net.TransportInfo
+ * @see NetworkCapabilities#getTransportInfo()
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
 public final class VpnTransportInfo implements TransportInfo, Parcelable {
     private static final SparseArray<String> sTypeToString =
             MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 8fc3181..ed1716f 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,6 @@
     ],
     srcs: [
         "jni/com_android_server_TestNetworkService.cpp",
-        "jni/com_android_server_connectivity_Vpn.cpp",
         "jni/onload.cpp",
     ],
     shared_libs: [
diff --git a/packages/Connectivity/service/jni/onload.cpp b/packages/Connectivity/service/jni/onload.cpp
index 3afcb0e..0012879 100644
--- a/packages/Connectivity/service/jni/onload.cpp
+++ b/packages/Connectivity/service/jni/onload.cpp
@@ -19,7 +19,6 @@
 
 namespace android {
 
-int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_TestNetworkService(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -29,12 +28,11 @@
         return JNI_ERR;
     }
 
-    if (register_android_server_connectivity_Vpn(env) < 0
-        || register_android_server_TestNetworkService(env) < 0) {
+    if (register_android_server_TestNetworkService(env) < 0) {
         return JNI_ERR;
     }
 
     return JNI_VERSION_1_6;
 }
 
-};
\ No newline at end of file
+};
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 889980a..577fb66 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Beëindig gastesessie"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gas"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data, drie stawe."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasein vol."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet is ontkoppel."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet gekoppel."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index c41e4d5..c554e2e 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"የእንግዳ ክፍለ-ጊዜ ጨርስ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"የውሂብ ሦስት አሞሌዎች።"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"የውሂብ አመልካች ሙሉ ነው።"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ኤተርኔት ተነቅሏል።"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ኤተርኔት ተገናኝቷል።"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ኢተርኔት።"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index f8d1d57..b23cebd 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -558,7 +558,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string>
     <string name="user_nickname" msgid="262624187455825083">"اللقب"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"إنهاء جلسة الضيف"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string>
@@ -595,5 +595,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"إشارة البيانات تتكون من ثلاثة أشرطة."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"إشارة البيانات كاملة."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"‏تم قطع اتصال Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"‏تم إنشاء اتصال Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"إيثرنت"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c6078f8..2380b2f 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ডেটা ছিংগনেলত তিনিডাল দণ্ড আছে।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ডেটা ছিগনেল পূৰা আছে।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথাৰনেট সংযোগ বিচ্ছিন্ন হৈছে।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথাৰনেট সংযোগ হৈছে।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথাৰনেট।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index d3e0a25..f70bb84 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Qonaq sessiyasını bitirin"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data üç xətdir."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data siqnalı tamdır."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kəsilib."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet qoşuludur."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 8541222..749f864 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal za podatke od tri crte."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za podatke je najjači."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa eternetom je prekinuta."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Eternet je povezan."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index aab600f..6b2740d 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завяршыць гасцявы сеанс"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"3 планкі дадзеных."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Поўны сігнал перадачы дадзеных."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet адлучаны."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet падлучаны."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 92f2935..1a24b9e 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Данните са с три чертички."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналът за данни е пълен."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Връзката с Ethernet е прекратена."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Установена е връзка с Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index b40e24e..1c9f84f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"গেস্ট সেশন শেষ করুন"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"তিন দন্ড ডেটার সংকেত৷"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"পূর্ণ ডেটার সংকেত রয়েছে৷"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথারনেটের সংযোগ বিচ্ছিন্ন হয়েছে৷"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথারনেট সংযুক্ত হয়েছে৷"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথারনেট।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cec2e45..f701421 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Prijenos podataka na tri crtice."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za prijenos podataka pun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa Ethernetom je prekinuta."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet je spojen."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 6134da8..78ef109 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Àlies"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalitza la sessió de convidat"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Senyal de dades: tres barres."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Senyal de dades: complet."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"S\'ha desconnectat l\'Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connectada"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index f29a3dd..8ca9800 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončení relace hosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Host"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tři čárky signálu datové sítě."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál datové sítě."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Síť ethernet je odpojena."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Síť ethernet je připojena."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d92d41d..5d55a12 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Afslut gæstesessionen"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tre bjælker."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal fuldt."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er ikke tilsluttet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilsluttet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index ac05b620..1fbb391 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alias"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsitzung beenden"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datensignal - drei Balken"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Volle Datensignalstärke"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet nicht verbunden"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbunden"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 0b11d69..32bcf87 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Λήξη περιόδου σύνδεσης επισκέπτη"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Τρεις γραμμές δεδομένων."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Πλήρες σήμα δεδομένων."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Το Ethernet αποσυνδέθηκε."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Το Ethernet συνδέθηκε."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index aa0d3f1..198fee4 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index c01f3a0..27cd8b3 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎Creating new user…‎‏‎‎‏‎"</string>
     <string name="user_nickname" msgid="262624187455825083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎Nickname‎‏‎‎‏‎"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎Add guest‎‏‎‎‏‎"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎End guest session‎‏‎‎‏‎"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎Remove guest‎‏‎‎‏‎"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎Guest‎‏‎‎‏‎"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎Take a photo‎‏‎‎‏‎"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎Choose an image‎‏‎‎‏‎"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎Data three bars.‎‏‎‎‏‎"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎Data signal full.‎‏‎‎‏‎"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎Ethernet disconnected.‎‏‎‎‏‎"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎Ethernet connected.‎‏‎‎‏‎"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎Ethernet.‎‏‎‎‏‎"</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎No calling.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 1da8e37..0f76576 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos completa"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 54226ce..abd209c 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apodo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos al máximo"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Conexión Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conexión Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 4c747e3..2e9f3b8 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Lõpeta külastajaseanss"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Andmeside: kolm pulka."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Andmesignaal on tugev."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Etherneti-ühendus on katkestatud."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Etherneti-ühendus on loodud."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 6016cd2..ecb4f75 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datu-seinaleak hiru barra ditu."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datu-seinale osoa."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bidezko konexioa eten da."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bidez konektatu da."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index e855570..56ade54 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string>
     <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"پایان دادن به جلسه مهمان"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"سه نوار برای داده."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"قدرت سیگنال داده کامل است."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"اترنت قطع شد."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"اترنت متصل شد."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"اترنت."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 724e52a..e26ecb7 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Lopeta Vierailija-käyttökerta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datasignaali - kolme palkkia"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Vahva kuuluvuus."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet on irrotettu."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet on yhdistetty."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 74c3005..d61ebc0 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index c9651ce..64c06fa 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Fermer la session Invité"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f499f58..cf63dd7 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alcume"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de sinal de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de datos: completo"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Desconectouse a Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conectouse a Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 23ee1de..427ce56 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"અતિથિ સત્ર સમાપ્ત કરો"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ડેટા ત્રણ બાર."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ડેટા સિગ્નલ પૂર્ણ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ઇથરનેટ ડિસ્કનેક્ટ થયું."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ઇથરનેટ કનેક્ટ થયું."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ઇથરનેટ."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index a2fc43c..159d2db 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string>
     <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सि‍ग्‍नल पूरा."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ईथरनेट डिस्‍कनेक्‍ट किया गया."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ईथरनेट कनेक्‍ट किया गया."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ईथरनेट."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 396051d..5e33ef3 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi gostujuću sesiju"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatkovni signal tri stupca."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal pun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Prekinuta je veza s ethernetom."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Uspostavljena je veza s ethernetom."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 0c9bc54..28975be 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Becenév"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"A vendég munkamenet befejezése"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Adat három sáv."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Adatjel teljes."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet leválasztva."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet csatlakoztatva."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 56c93b6..a207526 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Տվյալների երեք գիծ:"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Տվյալների ազդանշանը լրիվ է:"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet-ը անջատված է:"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet-ը կապակցված է:"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet։"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 0440f47..c4d752d 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Akhiri sesi tamu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga batang."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinyal data penuh."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet terputus."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tersambung."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 0bb294f..56b0a81 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ljúka gestalotu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sendistyrkur gagnatengingar er þrjú strik."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Fullur sendistyrkur gagnatengingar."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet aftengt."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tengt."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 54049d7..f045d27 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Termina sessione Ospite"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string>
@@ -577,7 +577,7 @@
     <string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string>
     <string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
     <string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
-    <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"Wi-Fi operatore"</string>
+    <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
     <string name="cell_data_off_content_description" msgid="2280700839891636498">"Dati mobili disattivati"</string>
     <string name="not_default_data_content_description" msgid="6517068332106592887">"Non impostato per l\'utilizzo dei dati"</string>
     <string name="accessibility_no_phone" msgid="2687419663127582503">"Nessun telefono."</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: tre barre."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Massimo segnale dati."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Connessione Ethernet annullata."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Connessione Ethernet stabilita."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 6cc4347..ba3a467 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string>
     <string name="user_nickname" msgid="262624187455825083">"כינוי"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"הפסקת הגלישה כאורח"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"שלושה פסים של נתונים."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"אות הנתונים מלא."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"אתרנט מנותק."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"אתרנט מחובר."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"אתרנט."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index ec674ad..6f398b6 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"データ信号:レベル3"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"データ信号:フル"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"イーサネット接続を解除しました。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"イーサネットに接続しました。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"イーサネット。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 30ab3b4..5bf1dd0 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
     <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"სტუმრის სესიის დასრულება"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"მონაცემების გადაცემა: სამი ზოლი"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"მონაცემთა გადაცემის საიმედო სიგნალი."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet კავშირი შეწყვეტილია."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet დაკავშირებულია."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index ef78527..c7c3837 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Қонақ сеансын аяқтау"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дерекқор үш баған."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дерекқор сигналы толы."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажыратылған."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet қосылған."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index ed59c74..99a73e1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើត​អ្នកប្រើប្រាស់ថ្មី…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះ​ហៅក្រៅ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូល​ភ្ញៀវ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"បញ្ចប់វគ្គភ្ញៀវ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"លុប​​​ភ្ញៀវ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើស​រូបភាព"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ទិន្នន័យ​បី​កាំ។"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"សញ្ញា​ទិន្នន័យ​ពេញ។"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"បានផ្តាច់អ៊ីសឺរណិត។"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"បានភ្ជាប់អ៊ីសឺរណិត។"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"អ៊ីសឺរណិត។"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 995dc86..54a42a8 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸಿ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ಡೇಟಾ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ಡೇಟಾ ಸಂಕೇತ ತುಂಬಿದೆ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ಇಥರ್ನೆಟ್."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 7863d48..9e9fea9 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string>
     <string name="user_nickname" msgid="262624187455825083">"닉네임"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"게스트 세션 종료"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"데이터 신호 막대가 세 개입니다."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"데이터 신호가 강합니다."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"이더넷에서 연결 해제되었습니다."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"이더넷에 연결되었습니다."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"이더넷에 연결되었습니다."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 6af32cc..7c0fbae 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -86,7 +86,7 @@
     <item msgid="8147982633566548515">"карта14"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
-    <item msgid="2494959071796102843">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
     <item msgid="720249083677397051">"AAC"</item>
     <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -94,7 +94,7 @@
     <item msgid="3825367753087348007">"LDAC"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_summaries">
-    <item msgid="8868109554557331312">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="9024885861221697796">"SBC"</item>
     <item msgid="4688890470703790013">"AAC"</item>
     <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -102,38 +102,38 @@
     <item msgid="2553206901068987657">"LDAC"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
-    <item msgid="926809261293414607">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
     <item msgid="3208896645474529394">"48,0 кГц"</item>
     <item msgid="8420261949134022577">"88,2 кГц"</item>
     <item msgid="8887519571067543785">"96,0 кГц"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
-    <item msgid="2284090879080331090">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2284090879080331090">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="1872276250541651186">"44,1 кГц"</item>
     <item msgid="8736780630001704004">"48,0 кГц"</item>
     <item msgid="7698585706868856888">"88,2 кГц"</item>
     <item msgid="8946330945963372966">"96,0 кГц"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
-    <item msgid="2574107108483219051">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2574107108483219051">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="4671992321419011165">"16 бит/үлгү"</item>
     <item msgid="1933898806184763940">"24 бит/үлгү"</item>
     <item msgid="1212577207279552119">"32 бит/үлгү"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
-    <item msgid="9196208128729063711">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="9196208128729063711">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="1084497364516370912">"16 бит/үлгү"</item>
     <item msgid="2077889391457961734">"24 бит/үлгү"</item>
     <item msgid="3836844909491316925">"32 бит/үлгү"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
-    <item msgid="3014194562841654656">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="3014194562841654656">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="5982952342181788248">"Моно"</item>
     <item msgid="927546067692441494">"Стерео"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
-    <item msgid="1997302811102880485">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="1997302811102880485">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8005696114958453588">"Моно"</item>
     <item msgid="1333279807604675720">"Стерео"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 45d05c6..2aec5cb 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -143,7 +143,7 @@
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
     <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Алынып салынган колдонмолор"</string>
     <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Өчүрүлгөн колдонмолор жана колдонуучулар"</string>
-    <string name="data_usage_ota" msgid="7984667793701597001">"Тутум жаңыртуулары"</string>
+    <string name="data_usage_ota" msgid="7984667793701597001">"Системанын жаңыртуулары"</string>
     <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB модем"</string>
     <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Wi-Fi байланыш түйүнү"</string>
     <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth модем"</string>
@@ -162,7 +162,7 @@
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Негизги тон"</string>
     <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Синтезделген кептин интонациясына таасирин тийгизет"</string>
     <string name="tts_default_lang_title" msgid="4698933575028098940">"Тил"</string>
-    <string name="tts_lang_use_system" msgid="6312945299804012406">"Тутум тилин колдонуу"</string>
+    <string name="tts_lang_use_system" msgid="6312945299804012406">"Системанын тилин колдонуу"</string>
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Тил тандалган жок"</string>
     <string name="tts_default_lang_summary" msgid="9042620014800063470">"Текстти окуй турган тилди тандоо"</string>
     <string name="tts_play_example_title" msgid="1599468547216481684">"Үлгүнү угуу"</string>
@@ -483,7 +483,7 @@
     <string name="retail_demo_reset_next" msgid="3688129033843885362">"Кийинки"</string>
     <string name="retail_demo_reset_title" msgid="1866911701095959800">"Сырсөз талап кылынат"</string>
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"Жигердүү киргизүү ыкмалары"</string>
-    <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Тутум тилдерин колдонуу"</string>
+    <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Системанын тилдерин колдонуу"</string>
     <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> тууралоолору ачылган жок"</string>
     <string name="ime_security_warning" msgid="6547562217880551450">"Бул киргизүү ыкмасы сиз терген бардык тексттер, сырсөздөр жана кредиттик  карталар сыяктуу жеке маалыматтарды кошо чогултушу мүмкүн. Бул <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> колдонмосу менен байланыштуу. Ушул киргизүү ыкма колдонулсунбу?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Эскертүү: Өчүрүп-күйгүзгөндөн кийин, бул колдонмо телефондун кулпусу ачылмайынча иштебейт"</string>
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Конок сеансын бүтүрүү"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Мобилдик интернеттин сигналы үч таякча."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Мобилдик интернеттин сигналы толук."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажырады."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet туташты."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index ca6e06c..e199e1f 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ສິ້ນສຸດເຊດຊັນແຂກ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ຂໍ້ມູນສາມຂີດ."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ສັນ​ຍານຂໍ້ມູນ​ເຕັມ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ອີ​ເທີ​ເນັດ​ຕັດ​ເຊື່ອມ​ຕໍ່​ແລ້ວ."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ອີ​ເທີ​ເນັດ​ເຊື່ອມ​ຕໍ່​ແລ້ວ."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ອີເທີເນັດ."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 173b57e..a25c59f5 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Baigti svečio sesiją"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Trys duomenų juostos."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Stiprus duomenų signalas."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Atsijungta nuo eterneto."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Prijungta prie eterneto."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternetas."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index a8bd2cc..f360c90 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Beigt viesa sesiju"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: trīs joslas."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Pilna piekļuve datu signālam."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Pārtraukts savienojums ar tīklu Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Izveidots savienojums ar tīklu Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Tīkls Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0ef3f0e..a32d00b 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Прекар"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши ја гостинската сесија"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Податоци три цртички."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналот за податоци е исполнет."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Етернетот е исклучен."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернетот е поврзан."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e6289ae..902507d 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്‌ടിക്കുന്നു…"</string>
     <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"അതിഥി സെഷൻ അവസാനിപ്പിക്കുക"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ഡാറ്റ മൂന്ന് ബാർ."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ഡാറ്റ സിഗ്‌നൽ പൂർണ്ണമാണ്."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ഇതർനെറ്റ് വിച്ഛേദിച്ചു."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ഇതർനെറ്റ് കണക്റ്റുചെയ്‌തു."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ഇതർനെറ്റ്."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ead712c..83009b7 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Хоч"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дата гурван баганатай."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дата дохио дүүрэн."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet саллаа."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet холбогдсон."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Этернэт."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index edcc71b..359f525 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string>
     <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथी सत्र संपवा"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूर्ण."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट डिस्कनेक्ट केले."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट कनेक्ट केले."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 8a14437..eba89da 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga bar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Isyarat data penuh."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet diputuskan sambungan."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet disambungkan."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 377c8b2..e982aa9 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string>
     <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ဧည့်သည်ဆက်ရှင်ကို အဆုံးသတ်ရန်"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ဒေတာသုံးဘား။"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ဒေတာထုတ်လွှင့်မှုအပြည့်ဖမ်းမိခြင်း"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet နှင့်ချိတ်ဆက်မှုပြတ်တောက်"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ချိတ်ဆက်ထား။"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"အီသာနက်။"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index f0e773b..abbb5e7 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Avslutt gjesteøkten"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data – tre stolper."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal er fullt."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er frakoblet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilkoblet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 0bde70f..d7a32bf 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string>
     <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथिको सत्र अन्त्य गर्नुहोस्"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तिन बाधाहरू।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा संकेत पूर्ण।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट विच्छेद भयो।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट जोडियो।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2bc1e8c..7e739bc 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Gegevens: drie streepjes."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Gegevenssignaal is op volle sterkte."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetverbinding verbroken."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbonden."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 787c871..1e842b7 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ଅତିଥି ସେସନ୍ ଶେଷ କରନ୍ତୁ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ଡାଟାର ତିନୋଟି ବାର୍‍ ଅଛି।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ଡାଟା ସିଗ୍ନାଲ୍ ପୂର୍ଣ୍ଣ ଅଛି।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ଇଥରନେଟ୍‍ ବିଚ୍ଛିନ୍ନ ହୋଇଛି।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ଇଥରନେଟ୍‍ ସଂଯୁକ୍ତ ହୋଇଛି।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9483e06..d035fc0 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਸਮਾਪਤ ਕਰੋ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">" ਡਾਟਾ  ਤਿੰਨ ਬਾਰ।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">" ਡਾਟਾ  ਸਿਗਨਲ ਪੂਰਾ।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ਈਥਰਨੈੱਟ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ਈਥਰਨੈੱਟ ਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ਈਥਰਨੈੱਟ।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index c7f1595..8a732cb 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Kończenie sesji gościa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dane: trzy paski."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Dane: pełna moc sygnału."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Rozłączono z siecią Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Połączono z siecią Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 94cad918..ab23059 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Terminar a sessão de convidado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados completo."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desligada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ligada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 63d8b8f..98edb32 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Încheiați sesiunea pentru invitați"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Semnal pentru date: trei bare."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Semnal pentru date: complet."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet deconectat."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectat."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index ac43e49..972dc20 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завершить гостевой сеанс"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал передачи данных: три деления."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Надежный сигнал передачи данных."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Устройство отключено от Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Устройство подключено к Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 08fd9a0..0dee8e1 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string>
     <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ආරාධිත සැසිය අවසන් කරන්න"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"දත්ත තීරු 3."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"දත්ත සංඥාව පිරී ඇත."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ඊතර්නෙට් විසන්ධි කරන ලදී."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ඊතර්නෙට් සම්බන්ධ කරන ලදී."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ඊතර්නෙට්."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 8f05684..7d49ca3 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončiť reláciu hosťa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tri čiarky signálu dátovej siete."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál dátovej siete."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Sieť ethernet je odpojená"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Sieť ethernet je pripojená"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 3dccf3b..48e5dcc 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Končaj sejo gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatki s tremi črticami."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal poln."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetna povezava je prekinjena."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernetna povezava je vzpostavljena."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c09ad4c..0bc905e 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sinjali është me tre vija."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinjali i të dhënave është i plotë."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Lidhja e eternetit u shkëput."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Lidhja e eternetit u lidh."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b5a91bf..6d19af8 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Надимак"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши сесију госта"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал за податке од три црте."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигнал за податке је најјачи."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Веза са етернетом је прекинута."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернет је повезан."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 16a1be6..944c423 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Avsluta gästsession"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data: tre staplar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignalen är full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet har kopplats från."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet har anslutits."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 58896c8..442f54b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Maliza kipindi cha mgeni"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Fito tatu za habari."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Ishara ya data imejaa."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethaneti imeondolewa."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethaneti imeunganishwa."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethaneti."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 71cf7d4..561a122 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string>
     <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"விருந்தினர் அமர்வை நிறைவுசெய்"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"தரவு சிக்னல் மூன்று கோட்டில் உள்ளது."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"தரவு சிக்னல் முழுமையாக உள்ளது."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ஈத்தர்நெட் துண்டிக்கப்பட்டது."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ஈத்தர்நெட் இணைக்கப்பட்டது."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ஈதர்நெட்."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c3a65e7..18d7f28 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్‌ను క్రియేట్ చేస్తోంది…"</string>
     <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"గెస్ట్ సెషన్‌ను ముగించు"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్‌ను ఎంచుకోండి"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"డేటా మూడు బార్లు."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"డేటా సిగ్నల్ సంపూర్ణంగా ఉంది."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ఈథర్‌నెట్ డిస్‌కనెక్ట్ చేయబడింది."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ఈథర్‌నెట్ కనెక్ట్ చేయబడింది."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ఈథర్‌నెట్."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 903accb..ceab26c 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"สัญญาณข้อมูลสามขีด"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"สัญญาณข้อมูลเต็ม"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ยกเลิกการเชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"เชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"อีเทอร์เน็ต"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index b259201..671fa14 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data na tatlong bar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Puno ang signal ng data."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Nadiskonekta ang Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Nakakonekta ang Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index a0bc362..98d89f3 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Veri sinyali üç çubuk."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Veri sinyali tam."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kesildi."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bağlandı."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 509c8a6..4e739ac 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завершити сеанс у режимі \"Гість\""</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Три смужки сигналу даних."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Максимальний сигнал даних."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet відключено."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet підключено."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e097ab2..c7dee95 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string>
     <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"مہمان سیشن ختم کریں"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ڈیٹا کے تین بارز۔"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ڈیٹا سگنل بھرا ہوا ہے۔"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ایتھرنیٹ منقطع ہے۔"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ایتھرنیٹ منسلک ہے۔"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ایتھرنیٹ۔"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 8c2a95d..433096b 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nik"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Ma’lumotlar uchta panelda."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Internet signali butun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Qurilma Ethernet tarmog‘idan uzildi."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Qurilma Ethernet tarmog‘iga ulandi."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7a4d89b..a14462b 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tín hiệu dữ liệu ba vạch."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Tín hiệu dữ liệu đầy đủ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Đã ngắt kết nối Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Đã kết nối Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7cd61fc..f6bea91 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string>
     <string name="user_nickname" msgid="262624187455825083">"昵称"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"结束访客会话"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"访客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"数据信号强度为三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"数据信号满格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太网已断开连接。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"以太网已连接。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太网。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 458071c..3cf15a9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網絡訊號強度為三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網絡訊號滿格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太網連接中斷。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連接以太網。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太網絡。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 867ed8b..7cf0297 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網路訊號強度三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網路訊號滿格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"未連上乙太網路。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連上乙太網路。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"乙太網路。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e64dbd3..d950d58 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Misa isikhathi sesihambeli"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Amabha amathathu edatha"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Igcwele i-signal yedatha"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"I-Ethernet inqanyuliwe."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"I-Ethernet ixhunyiwe."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"I-Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8ff6d07..3866151 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1380,7 +1380,7 @@
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
     <string name="guest_new_guest">Add guest</string>
     <!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest">End guest session</string>
+    <string name="guest_exit_guest">Remove guest</string>
     <!-- Name for the guest user [CHAR LIMIT=35] -->
     <string name="guest_nickname">Guest</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index 64cb0f1..43717ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -196,7 +196,7 @@
     }
 
     private void stopTrackingWifiRestart() {
-        mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+        mWifiManager.unregisterSubsystemRestartTrackingCallback(
                 mWifiSubsystemRestartTrackingCallback);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 78282fb..841a49e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -39,6 +39,8 @@
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -47,6 +49,8 @@
  * Track status of Wi-Fi for the Sys UI.
  */
 public class WifiStatusTracker {
+    private static final int HISTORY_SIZE = 32;
+    private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
     private final Context mContext;
     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
     private final WifiManager mWifiManager;
@@ -54,6 +58,10 @@
     private final ConnectivityManager mConnectivityManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Set<Integer> mNetworks = new HashSet<>();
+    // Save the previous HISTORY_SIZE states for logging.
+    private final String[] mHistory = new String[HISTORY_SIZE];
+    // Where to copy the next state into.
+    private int mHistoryIndex;
     private final WifiNetworkScoreCache.CacheListener mCacheListener =
             new WifiNetworkScoreCache.CacheListener(mHandler) {
                 @Override
@@ -93,6 +101,13 @@
             } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
             }
+            String log = new StringBuilder()
+                    .append(SSDF.format(System.currentTimeMillis())).append(",")
+                    .append("onCapabilitiesChanged: ")
+                    .append("network=").append(network).append(",")
+                    .append("networkCapabilities=").append(networkCapabilities)
+                    .toString();
+            recordLastWifiNetwork(log);
             if (wifiInfo != null) {
                 updateWifiInfo(wifiInfo);
                 updateStatusLabel();
@@ -102,6 +117,12 @@
 
         @Override
         public void onLost(Network network) {
+            String log = new StringBuilder()
+                    .append(SSDF.format(System.currentTimeMillis())).append(",")
+                    .append("onLost: ")
+                    .append("network=").append(network)
+                    .toString();
+            recordLastWifiNetwork(log);
             if (mNetworks.contains(network.getNetId())) {
                 mNetworks.remove(network.getNetId());
                 updateWifiInfo(null);
@@ -336,4 +357,25 @@
         }
         return null;
     }
+
+    private void recordLastWifiNetwork(String log) {
+        mHistory[mHistoryIndex] = log;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
+    }
+
+    /** Dump function. */
+    public void dump(PrintWriter pw) {
+        pw.println("  - WiFi Network History ------");
+        int size = 0;
+        for (int i = 0; i < HISTORY_SIZE; i++) {
+            if (mHistory[i] != null) size++;
+        }
+        // Print out the previous states in ordered number.
+        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+            pw.println("  Previous WiFiNetwork("
+                    + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+                    + mHistory[i & (HISTORY_SIZE - 1)]);
+        }
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a6e2af9..9cd7083 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -24,6 +24,7 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
@@ -212,7 +213,6 @@
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) throws IOException {
-
         byte[] systemSettingsData = getSystemSettings();
         byte[] secureSettingsData = getSecureSettings();
         byte[] globalSettingsData = getGlobalSettings();
@@ -1204,17 +1204,25 @@
     }
 
     private byte[] getSimSpecificSettingsData() {
-        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
-        byte[] simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
-        Log.i(TAG, "sim specific data of length + " + simSpecificData.length
+        byte[] simSpecificData = new byte[0];
+        PackageManager packageManager = getBaseContext().getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+            simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
+            Log.i(TAG, "sim specific data of length + " + simSpecificData.length
                 + " successfully retrieved");
+        }
 
         return simSpecificData;
     }
 
     private void restoreSimSpecificSettings(byte[] data) {
-        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
-        subManager.restoreAllSimSpecificSettingsFromBackup(data);
+        PackageManager packageManager = getBaseContext().getPackageManager();
+        boolean hasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+        if (hasTelephony) {
+            SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+            subManager.restoreAllSimSpecificSettingsFromBackup(data);
+        }
     }
 
     private void updateWindowManagerIfNeeded(Integer previousDensity) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 438cec8..6719f17 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -323,6 +323,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
                     Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
                     Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
                     Settings.Global.LOCK_SOUND,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1566b76..35a4e81 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -402,6 +402,9 @@
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
     <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
 
+    <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+    <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7bfb42b..bf5198e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -194,6 +194,5 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
-        "checked-wm_shell_protolog.json",
     ],
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index d43aaf0..beee03b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -46,6 +46,21 @@
         return true;
     }
 
+    /**
+     * @return if detail panel should animate when shown or closed
+     */
+    default boolean shouldAnimate() {
+        return true;
+    }
+
+    /**
+     * @return true if the callback handled the event and wants to keep the detail panel open, false
+     * otherwise. Returning false will close the panel.
+     */
+    default boolean onDoneButtonClicked() {
+        return false;
+    }
+
     default UiEventLogger.UiEventEnum openDetailEvent() {
         return INVALID;
     }
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
new file mode 100644
index 0000000..3938b73
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.phone.UserAvatarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_qs_user_switch_view"
+    android:layout_width="@dimen/kg_framed_avatar_size"
+    android:layout_height="@dimen/kg_framed_avatar_size"
+    android:layout_centerHorizontal="true"
+    android:layout_gravity="center_horizontal|bottom"
+    systemui:avatarPadding="0dp"
+    systemui:badgeDiameter="18dp"
+    systemui:badgeMargin="1dp"
+    systemui:frameColor="@color/kg_user_avatar_frame"
+    systemui:framePadding="0dp"
+    systemui:frameWidth="0dp">
+</com.android.systemui.statusbar.phone.UserAvatarView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 8a47a22..95cee66 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -47,7 +47,7 @@
         android:layout_width="wrap_content"
         android:layout_height="48dp"
         android:layout_marginBottom="4dp"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:forceHasOverlappingRendering="false"
     />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 6b42705..a4cf5ed 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -48,7 +48,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textColor="@color/media_primary_text"
+            android:textColor="?android:attr/textColorPrimary"
             android:gravity="start"
             android:textSize="14sp" />
 
@@ -58,7 +58,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentEnd="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textColor="@color/media_primary_text"
+            android:textColor="?android:attr/textColorPrimary"
             android:gravity="end"
             android:textSize="14sp" />
     </FrameLayout>
@@ -120,13 +120,13 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        android:gravity="center_vertical|end"
+        android:gravity="center"
+        android:background="@drawable/qs_media_light_source"
         android:forceHasOverlappingRendering="false">
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:foreground="@drawable/qs_media_seamless_background"
-            android:background="@drawable/qs_media_light_source"
+            android:background="@drawable/qs_media_seamless_background"
             android:orientation="horizontal"
             android:padding="6dp"
             android:contentDescription="@string/quick_settings_media_device_label">
@@ -135,7 +135,7 @@
                 android:layout_width="@dimen/qs_seamless_icon_size"
                 android:layout_height="@dimen/qs_seamless_icon_size"
                 android:layout_gravity="center"
-                android:tint="@color/media_primary_text"
+                android:tint="?android:attr/colorPrimary"
                 android:src="@*android:drawable/ic_media_seamless" />
             <TextView
                 android:visibility="gone"
@@ -147,7 +147,7 @@
                 android:fontFamily="@*android:string/config_headlineFontFamily"
                 android:singleLine="true"
                 android:text="@*android:string/ext_media_seamless_action"
-                android:textColor="@color/media_primary_text"
+                android:textColor="?android:attr/colorPrimary"
                 android:textDirection="locale"
                 android:textSize="14sp" />
         </LinearLayout>
@@ -157,7 +157,7 @@
         android:id="@+id/media_seamless_fallback"
         android:layout_width="@dimen/qs_seamless_icon_size"
         android:layout_height="@dimen/qs_seamless_icon_size"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/ic_cast_connected"
         android:forceHasOverlappingRendering="false" />
 
@@ -171,15 +171,15 @@
         android:clickable="true"
         android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
         android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding"
-        android:thumbTint="@color/media_primary_text"
-        android:progressTint="@color/media_seekbar_progress"
-        android:progressBackgroundTint="@color/media_disabled"
+        android:thumbTint="?android:attr/textColorPrimary"
+        android:progressTint="?android:attr/textColorPrimary"
+        android:progressBackgroundTint="?android:attr/colorBackground"
         android:splitTrack="false" />
 
     <!-- App name -->
     <TextView
         android:id="@+id/app_name"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:singleLine="true"
@@ -194,7 +194,7 @@
         android:layout_height="wrap_content"
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         android:singleLine="true"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:textSize="16sp" />
 
     <!-- Artist name -->
@@ -204,12 +204,12 @@
         android:layout_height="wrap_content"
         android:fontFamily="@*android:string/config_headlineFontFamily"
         android:singleLine="true"
-        android:textColor="@color/media_secondary_text"
+        android:textColor="?android:attr/textColorSecondary"
         android:textSize="14sp" />
 
     <com.android.internal.widget.CachingIconView
         android:id="@+id/icon"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_margin="6dp" />
@@ -223,7 +223,7 @@
         android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
         android:id="@+id/media_text"
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorSecondary"
         android:text="@string/controls_media_title"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
@@ -238,7 +238,7 @@
         android:id="@+id/remove_text"
         android:fontFamily="@*android:string/config_headlineFontFamily"
         android:singleLine="true"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:text="@string/controls_media_close_session"
         app:layout_constraintTop_toBottomOf="@id/media_text"
         app:layout_constraintStart_toStartOf="parent"
@@ -262,7 +262,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/controls_media_settings_button" />
     </FrameLayout>
 
@@ -283,7 +283,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/cancel" />
     </FrameLayout>
 
@@ -304,7 +304,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/controls_media_dismiss_button"
         />
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 859d904..a8ef7c3 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -32,6 +32,12 @@
         android:visibility="gone" />
 
     <ViewStub
+        android:id="@+id/keyguard_qs_user_switch_stub"
+        android:layout="@layout/keyguard_qs_user_switch"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
+    <ViewStub
         android:id="@+id/keyguard_user_switcher_stub"
         android:layout="@layout/keyguard_user_switcher"
         android:layout_height="match_parent"
@@ -63,6 +69,13 @@
             systemui:layout_constraintEnd_toEndOf="parent"
         />
 
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/qs_edge_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            systemui:layout_constraintGuide_percent="0.5"
+            android:orientation="vertical"/>
+
         <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
             android:layout_marginTop="@dimen/notification_panel_margin_top"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8bd9de9..acd671c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -244,11 +244,8 @@
     <color name="magnification_switch_button_color">#7F000000</color>
 
     <!-- media -->
-    <color name="media_primary_text">@android:color/white</color>
-    <color name="media_secondary_text">#99ffffff</color> <!-- 60% -->
-    <color name="media_seekbar_progress">#c0ffffff</color>
     <color name="media_disabled">#80ffffff</color>
-    <color name="media_seamless_border">#26ffffff</color> <!-- 15% -->
+    <color name="media_seamless_border">?android:attr/colorAccent</color>
     <color name="media_divider">#1d000000</color>
 
     <!-- controls -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6196225..78927f8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -279,6 +279,11 @@
     <!-- Whether to show the full screen user switcher. -->
     <bool name="config_enableFullscreenUserSwitcher">false</bool>
 
+    <!-- Whether the multi-user switch on the keyguard opens QS user panel. If false, clicking the
+         user switch on the keyguard will replace the notifications and status area with the user
+         switcher. The multi-user switch is only shown if config_keyguardUserSwitcher=false. -->
+    <bool name="config_keyguard_user_switch_opens_qs_details">false</bool>
+
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 594fbdf..0d92aea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -802,7 +802,7 @@
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">54dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
-    <dimen name="kg_framed_avatar_size">32dp</dimen>
+    <dimen name="kg_framed_avatar_size">48dp</dimen>
 
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6d731f8..d4bb128 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,5 +38,8 @@
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
 
+    <!-- The new animations to/from lockscreen and AOD! -->
+    <bool name="flag_lockscreen_animations">false</bool>
+
     <bool name="flag_toast_style">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7c1c24b..d997ca2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1116,13 +1116,13 @@
     <string name="guest_exit_button">End guest session</string>
 
     <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
-    <string name="guest_exit_guest_dialog_title">End guest session?</string>
+    <string name="guest_exit_guest_dialog_title">Remove guest?</string>
 
     <!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
     <string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
 
     <!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest_dialog_remove">End session</string>
+    <string name="guest_exit_guest_dialog_remove">Remove</string>
 
     <!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
     <string name="guest_wipe_session_title">Welcome back, guest!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 85c470f..14b376a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -582,7 +582,7 @@
 
     <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
         <item name="android:background">@drawable/qs_media_light_source</item>
-        <item name="android:tint">@android:color/white</item>
+        <item name="android:tint">?android:attr/textColorPrimary</item>
         <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
     </style>
 
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index f834d6d..f83e3a1 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -36,25 +36,23 @@
         app:layout_constraintTop_toTopOf="@id/icon"
         app:layout_constraintBottom_toBottomOf="@id/icon"
         app:layout_constraintStart_toEndOf="@id/icon"
-        app:layout_constraintEnd_toStartOf="@id/media_seamless"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
         app:layout_constrainedWidth="true"
         app:layout_constraintHorizontal_bias="0"
         />
 
     <Constraint
         android:id="@+id/media_seamless"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/app_name"
+        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
         app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintHorizontal_bias="1"
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="48dp"
         app:layout_constraintHeight_min="48dp"
-        android:layout_marginEnd="@dimen/qs_center_guideline_padding"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         />
 
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index d89e0eb..7c67720 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -36,25 +36,23 @@
         app:layout_constraintTop_toTopOf="@id/icon"
         app:layout_constraintBottom_toBottomOf="@id/icon"
         app:layout_constraintStart_toEndOf="@id/icon"
-        app:layout_constraintEnd_toStartOf="@id/media_seamless"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
         app:layout_constrainedWidth="true"
         app:layout_constraintHorizontal_bias="0"
         />
 
     <Constraint
         android:id="@+id/media_seamless"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/app_name"
+        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
         app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintHorizontal_bias="1"
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="48dp"
         app:layout_constraintHeight_min="48dp"
-        android:layout_marginEnd="@dimen/qs_center_guideline_padding"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         />
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index f6b03c1..e4f6e131 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,41 +24,14 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 import java.util.Locale;
 
 public class CarrierText extends TextView {
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "CarrierText";
+    private final boolean mShowMissingSim;
 
-    private static CharSequence mSeparator;
-
-    private boolean mShowMissingSim;
-
-    private boolean mShowAirplaneMode;
-    private boolean mShouldMarquee;
-
-    private CarrierTextController mCarrierTextController;
-
-    private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
-            new CarrierTextController.CarrierTextCallback() {
-                @Override
-                public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-                    setText(info.carrierText);
-                }
-
-                @Override
-                public void startedGoingToSleep() {
-                    setSelected(false);
-                }
-
-                @Override
-                public void finishedWakingUp() {
-                    setSelected(true);
-                }
-            };
+    private final boolean mShowAirplaneMode;
 
     public CarrierText(Context context) {
         this(context, null);
@@ -78,30 +51,6 @@
         }
         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
     }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mSeparator = getResources().getString(
-                com.android.internal.R.string.kg_text_message_separator);
-        mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
-                mShowMissingSim);
-        mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setSelected(mShouldMarquee); // Allow marquee to work.
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mCarrierTextController.setListening(mCarrierTextCallback);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mCarrierTextController.setListening(null);
-    }
-
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
@@ -113,7 +62,15 @@
         }
     }
 
-    private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+    public boolean getShowAirplaneMode() {
+        return mShowAirplaneMode;
+    }
+
+    public boolean getShowMissingSim() {
+        return mShowMissingSim;
+    }
+
+    private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
         private final Locale mLocale;
         private final boolean mAllCaps;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index b1e1434..46a6d8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,680 +16,61 @@
 
 package com.android.keyguard;
 
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-import static android.telephony.PhoneStateListener.LISTEN_NONE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.WirelessUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
+import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
 
 /**
- * Controller that generates text including the carrier names and/or the status of all the SIM
- * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
- * separated by a given separator {@link CharSequence}.
+ * Controller for {@link CarrierText}.
  */
-public class CarrierTextController {
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "CarrierTextController";
+public class CarrierTextController extends ViewController<CarrierText> {
+    private final CarrierTextManager mCarrierTextManager;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private final boolean mIsEmergencyCallCapable;
-    private final Handler mMainHandler;
-    private final Handler mBgHandler;
-    private boolean mTelephonyCapable;
-    private boolean mShowMissingSim;
-    private boolean mShowAirplaneMode;
-    private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
-    @VisibleForTesting
-    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private WifiManager mWifiManager;
-    private boolean[] mSimErrorState;
-    private final int mSimSlotsNumber;
-    @Nullable // Check for nullability before dispatching
-    private CarrierTextCallback mCarrierTextCallback;
-    private Context mContext;
-    private CharSequence mSeparator;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
-    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
-            new WakefulnessLifecycle.Observer() {
+    private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback =
+            new CarrierTextManager.CarrierTextCallback() {
                 @Override
-                public void onFinishedWakingUp() {
-                    final CarrierTextCallback callback = mCarrierTextCallback;
-                    if (callback != null) callback.finishedWakingUp();
+                public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
+                    mView.setText(info.carrierText);
                 }
 
                 @Override
-                public void onStartedGoingToSleep() {
-                    final CarrierTextCallback callback = mCarrierTextCallback;
-                    if (callback != null) callback.startedGoingToSleep();
+                public void startedGoingToSleep() {
+                    mView.setSelected(false);
+                }
+
+                @Override
+                public void finishedWakingUp() {
+                    mView.setSelected(true);
                 }
             };
 
-    @VisibleForTesting
-    protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onRefreshCarrierInfo() {
-            if (DEBUG) {
-                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
-                        + Boolean.toString(mTelephonyCapable));
-            }
-            updateCarrierText();
-        }
+    @Inject
+    public CarrierTextController(CarrierText view,
+            CarrierTextManager.Builder carrierTextManagerBuilder,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        super(view);
 
-        @Override
-        public void onTelephonyCapable(boolean capable) {
-            if (DEBUG) {
-                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
-                        + Boolean.toString(capable));
-            }
-            mTelephonyCapable = capable;
-            updateCarrierText();
-        }
-
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            if (slotId < 0 || slotId >= mSimSlotsNumber) {
-                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
-                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
-                return;
-            }
-
-            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
-            if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
-                mSimErrorState[slotId] = true;
-                updateCarrierText();
-            } else if (mSimErrorState[slotId]) {
-                mSimErrorState[slotId] = false;
-                updateCarrierText();
-            }
-        }
-    };
-
-    private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onActiveDataSubscriptionIdChanged(int subId) {
-            mActiveMobileDataSubscription = subId;
-            if (mNetworkSupported.get() && mCarrierTextCallback != null) {
-                updateCarrierText();
-            }
-        }
-    };
-
-    /**
-     * The status of this lock screen. Primarily used for widgets on LockScreen.
-     */
-    private enum StatusMode {
-        Normal, // Normal case (sim card present, it's not locked)
-        NetworkLocked, // SIM card is 'network locked'.
-        SimMissing, // SIM card is missing.
-        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
-        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
-        SimLocked, // SIM card is currently locked
-        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
-        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
-        SimIoError, // SIM card is faulty
-        SimUnknown // SIM card is unknown
+        mCarrierTextManager = carrierTextManagerBuilder
+                .setShowAirplaneMode(mView.getShowAirplaneMode())
+                .setShowMissingSim(mView.getShowMissingSim())
+                .build();
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
-    /**
-     * Controller that provides updates on text with carriers names or SIM status.
-     * Used by {@link CarrierText}.
-     *
-     * @param separator Separator between different parts of the text
-     */
-    public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
-            boolean showMissingSim) {
-        mContext = context;
-        mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
-
-        mShowAirplaneMode = showAirplaneMode;
-        mShowMissingSim = showMissingSim;
-
-        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mSeparator = separator;
-        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
-        mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
-        mSimErrorState = new boolean[mSimSlotsNumber];
-        mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
-        mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mBgHandler.post(() -> {
-            boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
-                    ConnectivityManager.TYPE_MOBILE);
-            if (supported && mNetworkSupported.compareAndSet(false, supported)) {
-                // This will set/remove the listeners appropriately. Note that it will never double
-                // add the listeners.
-                handleSetListening(mCarrierTextCallback);
-            }
-        });
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
     }
 
-    private TelephonyManager getTelephonyManager() {
-        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    @Override
+    protected void onViewAttached() {
+        mCarrierTextManager.setListening(mCarrierTextCallback);
     }
 
-    /**
-     * Checks if there are faulty cards. Adds the text depending on the slot of the card
-     *
-     * @param text:   current carrier text based on the sim state
-     * @param carrierNames names order by subscription order
-     * @param subOrderBySlot array containing the sub index for each slot ID
-     * @param noSims: whether a valid sim card is inserted
-     * @return text
-     */
-    private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
-            CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
-        final CharSequence carrier = "";
-        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
-                TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
-        // mSimErrorState has the state of each sim indexed by slotID.
-        for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
-            if (!mSimErrorState[index]) {
-                continue;
-            }
-            // In the case when no sim cards are detected but a faulty card is inserted
-            // overwrite the text and only show "Invalid card"
-            if (noSims) {
-                return concatenate(carrierTextForSimIOError,
-                        getContext().getText(
-                                com.android.internal.R.string.emergency_calls_only),
-                        mSeparator);
-            } else if (subOrderBySlot[index] != -1) {
-                int subIndex = subOrderBySlot[index];
-                // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
-                carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
-                        carrierNames[subIndex],
-                        mSeparator);
-            } else {
-                // concatenate "Invalid card" when faulty card is inserted in other slot
-                text = concatenate(text, carrierTextForSimIOError, mSeparator);
-            }
-
-        }
-        return text;
-    }
-
-    /**
-     * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
-     * (assumed false to start). In that case, the following happens:
-     * <ul>
-     *     <li> If there was a registered callback, and the network is supported, it will register
-     *          listeners.
-     *     <li> If there was not a registered callback, it will try to remove unregistered listeners
-     *          which is a no-op
-     * </ul>
-     *
-     * This call will always be processed in a background thread.
-     */
-    private void handleSetListening(CarrierTextCallback callback) {
-        TelephonyManager telephonyManager = getTelephonyManager();
-        if (callback != null) {
-            mCarrierTextCallback = callback;
-            if (mNetworkSupported.get()) {
-                // Keyguard update monitor expects callbacks from main thread
-                mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
-                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
-                telephonyManager.listen(mPhoneStateListener,
-                        LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
-            } else {
-                // Don't listen and clear out the text when the device isn't a phone.
-                mMainHandler.post(() -> callback.updateCarrierInfo(
-                        new CarrierTextCallbackInfo("", null, false, null)
-                ));
-            }
-        } else {
-            mCarrierTextCallback = null;
-            mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
-            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
-            telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
-        }
-    }
-
-    /**
-     * Sets the listening status of this controller. If the callback is null, it is set to
-     * not listening.
-     *
-     * @param callback Callback to provide text updates
-     */
-    public void setListening(CarrierTextCallback callback) {
-        mBgHandler.post(() -> handleSetListening(callback));
-    }
-
-    protected List<SubscriptionInfo> getSubscriptionInfo() {
-        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
-    }
-
-    protected void updateCarrierText() {
-        boolean allSimsMissing = true;
-        boolean anySimReadyAndInService = false;
-        CharSequence displayText = null;
-        List<SubscriptionInfo> subs = getSubscriptionInfo();
-
-        final int numSubs = subs.size();
-        final int[] subsIds = new int[numSubs];
-        // This array will contain in position i, the index of subscription in slot ID i.
-        // -1 if no subscription in that slot
-        final int[] subOrderBySlot = new int[mSimSlotsNumber];
-        for (int i = 0; i < mSimSlotsNumber; i++) {
-            subOrderBySlot[i] = -1;
-        }
-        final CharSequence[] carrierNames = new CharSequence[numSubs];
-        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
-
-        for (int i = 0; i < numSubs; i++) {
-            int subId = subs.get(i).getSubscriptionId();
-            carrierNames[i] = "";
-            subsIds[i] = subId;
-            subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
-            int simState = mKeyguardUpdateMonitor.getSimState(subId);
-            CharSequence carrierName = subs.get(i).getCarrierName();
-            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
-            if (DEBUG) {
-                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
-            }
-            if (carrierTextForSimState != null) {
-                allSimsMissing = false;
-                carrierNames[i] = carrierTextForSimState;
-            }
-            if (simState == TelephonyManager.SIM_STATE_READY) {
-                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
-                if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
-                    // hack for WFC (IWLAN) not turning off immediately once
-                    // Wi-Fi is disassociated or disabled
-                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                            || (mWifiManager.isWifiEnabled()
-                            && mWifiManager.getConnectionInfo() != null
-                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
-                        }
-                        anySimReadyAndInService = true;
-                    }
-                }
-            }
-        }
-        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
-        // This condition will also be true always when numSubs == 0
-        if (allSimsMissing && !anySimReadyAndInService) {
-            if (numSubs != 0) {
-                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
-                // This depends on mPlmn containing the text "Emergency calls only" when the radio
-                // has some connectivity. Otherwise, it should be null or empty and just show
-                // "No SIM card"
-                // Grab the first subscripton, because they all should contain the emergency text,
-                // described above.
-                displayText = makeCarrierStringOnEmergencyCapable(
-                        getMissingSimMessage(), subs.get(0).getCarrierName());
-            } else {
-                // We don't have a SubscriptionInfo to get the emergency calls only from.
-                // Grab it from the old sticky broadcast if possible instead. We can use it
-                // here because no subscriptions are active, so we don't have
-                // to worry about MSIM clashing.
-                CharSequence text =
-                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
-                Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
-                if (i != null) {
-                    String spn = "";
-                    String plmn = "";
-                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
-                        spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
-                    }
-                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
-                        plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
-                    }
-                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
-                    if (Objects.equals(plmn, spn)) {
-                        text = plmn;
-                    } else {
-                        text = concatenate(plmn, spn, mSeparator);
-                    }
-                }
-                displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
-            }
-        }
-
-        if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
-
-        displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
-                allSimsMissing);
-
-        boolean airplaneMode = false;
-        // APM (airplane mode) != no carrier state. There are carrier services
-        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
-        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
-            displayText = getAirplaneModeMessage();
-            airplaneMode = true;
-        }
-
-        final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
-                displayText,
-                carrierNames,
-                !allSimsMissing,
-                subsIds,
-                airplaneMode);
-        postToCallback(info);
-    }
-
-    @VisibleForTesting
-    protected void postToCallback(CarrierTextCallbackInfo info) {
-        final CarrierTextCallback callback = mCarrierTextCallback;
-        if (callback != null) {
-            mMainHandler.post(() -> callback.updateCarrierInfo(info));
-        }
-    }
-
-    private Context getContext() {
-        return mContext;
-    }
-
-    private String getMissingSimMessage() {
-        return mShowMissingSim && mTelephonyCapable
-                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
-    }
-
-    private String getAirplaneModeMessage() {
-        return mShowAirplaneMode
-                ? getContext().getString(R.string.airplane_mode) : "";
-    }
-
-    /**
-     * Top-level function for creating carrier text. Makes text based on simState, PLMN
-     * and SPN as well as device capabilities, such as being emergency call capable.
-     *
-     * @return Carrier text if not in missing state, null otherwise.
-     */
-    private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
-        CharSequence carrierText = null;
-        CarrierTextController.StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case Normal:
-                carrierText = text;
-                break;
-
-            case SimNotReady:
-                // Null is reserved for denoting missing, in this case we have nothing to display.
-                carrierText = ""; // nothing to display yet.
-                break;
-
-            case NetworkLocked:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        mContext.getText(R.string.keyguard_network_locked_message), text);
-                break;
-
-            case SimMissing:
-                carrierText = null;
-                break;
-
-            case SimPermDisabled:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(
-                                R.string.keyguard_permanent_disabled_sim_message_short),
-                        text);
-                break;
-
-            case SimMissingLocked:
-                carrierText = null;
-                break;
-
-            case SimLocked:
-                carrierText = makeCarrierStringOnLocked(
-                        getContext().getText(R.string.keyguard_sim_locked_message),
-                        text);
-                break;
-
-            case SimPukLocked:
-                carrierText = makeCarrierStringOnLocked(
-                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
-                        text);
-                break;
-            case SimIoError:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_sim_error_message_short),
-                        text);
-                break;
-            case SimUnknown:
-                carrierText = null;
-                break;
-        }
-
-        return carrierText;
-    }
-
-    /*
-     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
-     */
-    private CharSequence makeCarrierStringOnEmergencyCapable(
-            CharSequence simMessage, CharSequence emergencyCallMessage) {
-        if (mIsEmergencyCallCapable) {
-            return concatenate(simMessage, emergencyCallMessage, mSeparator);
-        }
-        return simMessage;
-    }
-
-    /*
-     * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
-     * DSDS
-     */
-    private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
-            CharSequence carrierName) {
-        final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
-        final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
-        if (simMessageValid && carrierNameValid) {
-            return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
-                    carrierName, simMessage);
-        } else if (simMessageValid) {
-            return simMessage;
-        } else if (carrierNameValid) {
-            return carrierName;
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Determine the current status of the lock screen given the SIM state and other stuff.
-     */
-    private CarrierTextController.StatusMode getStatusForIccState(int simState) {
-        final boolean missingAndNotProvisioned =
-                !mKeyguardUpdateMonitor.isDeviceProvisioned()
-                        && (simState == TelephonyManager.SIM_STATE_ABSENT
-                        || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
-
-        // Assume we're NETWORK_LOCKED if not provisioned
-        simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
-        switch (simState) {
-            case TelephonyManager.SIM_STATE_ABSENT:
-                return CarrierTextController.StatusMode.SimMissing;
-            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
-                return CarrierTextController.StatusMode.SimMissingLocked;
-            case TelephonyManager.SIM_STATE_NOT_READY:
-                return CarrierTextController.StatusMode.SimNotReady;
-            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
-                return CarrierTextController.StatusMode.SimLocked;
-            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
-                return CarrierTextController.StatusMode.SimPukLocked;
-            case TelephonyManager.SIM_STATE_READY:
-                return CarrierTextController.StatusMode.Normal;
-            case TelephonyManager.SIM_STATE_PERM_DISABLED:
-                return CarrierTextController.StatusMode.SimPermDisabled;
-            case TelephonyManager.SIM_STATE_UNKNOWN:
-                return CarrierTextController.StatusMode.SimUnknown;
-            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
-                return CarrierTextController.StatusMode.SimIoError;
-        }
-        return CarrierTextController.StatusMode.SimUnknown;
-    }
-
-    private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
-            CharSequence separator) {
-        final boolean plmnValid = !TextUtils.isEmpty(plmn);
-        final boolean spnValid = !TextUtils.isEmpty(spn);
-        if (plmnValid && spnValid) {
-            return new StringBuilder().append(plmn).append(separator).append(spn).toString();
-        } else if (plmnValid) {
-            return plmn;
-        } else if (spnValid) {
-            return spn;
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
-     * separator added so there are no extra separators that are not needed.
-     */
-    private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
-        int length = sequences.length;
-        if (length == 0) return "";
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < length; i++) {
-            if (!TextUtils.isEmpty(sequences[i])) {
-                if (!TextUtils.isEmpty(sb)) {
-                    sb.append(separator);
-                }
-                sb.append(sequences[i]);
-            }
-        }
-        return sb.toString();
-    }
-
-    private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
-        if (!TextUtils.isEmpty(string)) {
-            list.add(string);
-        }
-        return list;
-    }
-
-    private CharSequence getCarrierHelpTextForSimState(int simState,
-            String plmn, String spn) {
-        int carrierHelpTextId = 0;
-        CarrierTextController.StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case NetworkLocked:
-                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
-                break;
-
-            case SimMissing:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
-                break;
-
-            case SimPermDisabled:
-                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
-                break;
-
-            case SimMissingLocked:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
-                break;
-
-            case Normal:
-            case SimLocked:
-            case SimPukLocked:
-                break;
-        }
-
-        return mContext.getText(carrierHelpTextId);
-    }
-
-    public static class Builder {
-        private final Context mContext;
-        private final String mSeparator;
-        private boolean mShowAirplaneMode;
-        private boolean mShowMissingSim;
-
-        @Inject
-        public Builder(Context context, @Main Resources resources) {
-            mContext = context;
-            mSeparator = resources.getString(
-                    com.android.internal.R.string.kg_text_message_separator);
-        }
-
-
-        public Builder setShowAirplaneMode(boolean showAirplaneMode) {
-            mShowAirplaneMode = showAirplaneMode;
-            return this;
-        }
-
-        public Builder setShowMissingSim(boolean showMissingSim) {
-            mShowMissingSim = showMissingSim;
-            return this;
-        }
-
-        public CarrierTextController build() {
-            return new CarrierTextController(
-                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
-        }
-    }
-    /**
-     * Data structure for passing information to CarrierTextController subscribers
-     */
-    public static final class CarrierTextCallbackInfo {
-        public final CharSequence carrierText;
-        public final CharSequence[] listOfCarriers;
-        public final boolean anySimReady;
-        public final int[] subscriptionIds;
-        public boolean airplaneMode;
-
-        @VisibleForTesting
-        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
-                boolean anySimReady, int[] subscriptionIds) {
-            this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
-        }
-
-        @VisibleForTesting
-        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
-                boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
-            this.carrierText = carrierText;
-            this.listOfCarriers = listOfCarriers;
-            this.anySimReady = anySimReady;
-            this.subscriptionIds = subscriptionIds;
-            this.airplaneMode = airplaneMode;
-        }
-    }
-
-    /**
-     * Callback to communicate to Views
-     */
-    public interface CarrierTextCallback {
-        /**
-         * Provides updated carrier information.
-         */
-        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
-
-        /**
-         * Notifies the View that the device is going to sleep
-         */
-        default void startedGoingToSleep() {};
-
-        /**
-         * Notifies the View that the device finished waking up
-         */
-        default void finishedWakingUp() {};
+    @Override
+    protected void onViewDetached() {
+        mCarrierTextManager.setListening(null);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
new file mode 100644
index 0000000..87b01e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextManager {
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final String TAG = "CarrierTextController";
+
+    private final boolean mIsEmergencyCallCapable;
+    private final Handler mMainHandler;
+    private final Handler mBgHandler;
+    private boolean mTelephonyCapable;
+    private final boolean mShowMissingSim;
+    private final boolean mShowAirplaneMode;
+    private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
+    @VisibleForTesting
+    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final WifiManager mWifiManager;
+    private final boolean[] mSimErrorState;
+    private final int mSimSlotsNumber;
+    @Nullable // Check for nullability before dispatching
+    private CarrierTextCallback mCarrierTextCallback;
+    private final Context mContext;
+    private final TelephonyManager mTelephonyManager;
+    private final CharSequence mSeparator;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onFinishedWakingUp() {
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.finishedWakingUp();
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.startedGoingToSleep();
+                }
+            };
+
+    @VisibleForTesting
+    protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onRefreshCarrierInfo() {
+            if (DEBUG) {
+                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+                        + Boolean.toString(mTelephonyCapable));
+            }
+            updateCarrierText();
+        }
+
+        @Override
+        public void onTelephonyCapable(boolean capable) {
+            if (DEBUG) {
+                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+                        + Boolean.toString(capable));
+            }
+            mTelephonyCapable = capable;
+            updateCarrierText();
+        }
+
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            if (slotId < 0 || slotId >= mSimSlotsNumber) {
+                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+                return;
+            }
+
+            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+            if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
+                mSimErrorState[slotId] = true;
+                updateCarrierText();
+            } else if (mSimErrorState[slotId]) {
+                mSimErrorState[slotId] = false;
+                updateCarrierText();
+            }
+        }
+    };
+
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+                updateCarrierText();
+            }
+        }
+    };
+
+    /**
+     * The status of this lock screen. Primarily used for widgets on LockScreen.
+     */
+    private enum StatusMode {
+        Normal, // Normal case (sim card present, it's not locked)
+        NetworkLocked, // SIM card is 'network locked'.
+        SimMissing, // SIM card is missing.
+        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+        SimLocked, // SIM card is currently locked
+        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimIoError, // SIM card is faulty
+        SimUnknown // SIM card is unknown
+    }
+
+    /**
+     * Controller that provides updates on text with carriers names or SIM status.
+     * Used by {@link CarrierText}.
+     *
+     * @param separator Separator between different parts of the text
+     */
+    private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
+            boolean showMissingSim, @Nullable WifiManager wifiManager,
+            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            WakefulnessLifecycle wakefulnessLifecycle, @Main Handler mainHandler,
+            @Background Handler bgHandler, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        mContext = context;
+        mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+
+        mShowAirplaneMode = showAirplaneMode;
+        mShowMissingSim = showMissingSim;
+
+        mWifiManager = wifiManager;
+        mTelephonyManager = telephonyManager;
+        mSeparator = separator;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
+        mSimErrorState = new boolean[mSimSlotsNumber];
+        mMainHandler = mainHandler;
+        mBgHandler = bgHandler;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mBgHandler.post(() -> {
+            boolean supported = connectivityManager.isNetworkSupported(
+                    ConnectivityManager.TYPE_MOBILE);
+            if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+                // This will set/remove the listeners appropriately. Note that it will never double
+                // add the listeners.
+                handleSetListening(mCarrierTextCallback);
+            }
+        });
+    }
+
+    private TelephonyManager getTelephonyManager() {
+        return mTelephonyManager;
+    }
+
+    /**
+     * Checks if there are faulty cards. Adds the text depending on the slot of the card
+     *
+     * @param text:   current carrier text based on the sim state
+     * @param carrierNames names order by subscription order
+     * @param subOrderBySlot array containing the sub index for each slot ID
+     * @param noSims: whether a valid sim card is inserted
+     * @return text
+     */
+    private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
+            CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
+        final CharSequence carrier = "";
+        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
+        // mSimErrorState has the state of each sim indexed by slotID.
+        for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
+            if (!mSimErrorState[index]) {
+                continue;
+            }
+            // In the case when no sim cards are detected but a faulty card is inserted
+            // overwrite the text and only show "Invalid card"
+            if (noSims) {
+                return concatenate(carrierTextForSimIOError,
+                        getContext().getText(
+                                com.android.internal.R.string.emergency_calls_only),
+                        mSeparator);
+            } else if (subOrderBySlot[index] != -1) {
+                int subIndex = subOrderBySlot[index];
+                // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
+                carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
+                        carrierNames[subIndex],
+                        mSeparator);
+            } else {
+                // concatenate "Invalid card" when faulty card is inserted in other slot
+                text = concatenate(text, carrierTextForSimIOError, mSeparator);
+            }
+
+        }
+        return text;
+    }
+
+    /**
+     * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+     * (assumed false to start). In that case, the following happens:
+     * <ul>
+     *     <li> If there was a registered callback, and the network is supported, it will register
+     *          listeners.
+     *     <li> If there was not a registered callback, it will try to remove unregistered listeners
+     *          which is a no-op
+     * </ul>
+     *
+     * This call will always be processed in a background thread.
+     */
+    private void handleSetListening(CarrierTextCallback callback) {
+        TelephonyManager telephonyManager = getTelephonyManager();
+        if (callback != null) {
+            mCarrierTextCallback = callback;
+            if (mNetworkSupported.get()) {
+                // Keyguard update monitor expects callbacks from main thread
+                mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
+                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+                telephonyManager.listen(mPhoneStateListener,
+                        LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+            } else {
+                // Don't listen and clear out the text when the device isn't a phone.
+                mMainHandler.post(() -> callback.updateCarrierInfo(
+                        new CarrierTextCallbackInfo("", null, false, null)
+                ));
+            }
+        } else {
+            mCarrierTextCallback = null;
+            mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
+        }
+    }
+
+    /**
+     * Sets the listening status of this controller. If the callback is null, it is set to
+     * not listening.
+     *
+     * @param callback Callback to provide text updates
+     */
+    public void setListening(CarrierTextCallback callback) {
+        mBgHandler.post(() -> handleSetListening(callback));
+    }
+
+    protected List<SubscriptionInfo> getSubscriptionInfo() {
+        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+    }
+
+    protected void updateCarrierText() {
+        boolean allSimsMissing = true;
+        boolean anySimReadyAndInService = false;
+        CharSequence displayText = null;
+        List<SubscriptionInfo> subs = getSubscriptionInfo();
+
+        final int numSubs = subs.size();
+        final int[] subsIds = new int[numSubs];
+        // This array will contain in position i, the index of subscription in slot ID i.
+        // -1 if no subscription in that slot
+        final int[] subOrderBySlot = new int[mSimSlotsNumber];
+        for (int i = 0; i < mSimSlotsNumber; i++) {
+            subOrderBySlot[i] = -1;
+        }
+        final CharSequence[] carrierNames = new CharSequence[numSubs];
+        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+
+        for (int i = 0; i < numSubs; i++) {
+            int subId = subs.get(i).getSubscriptionId();
+            carrierNames[i] = "";
+            subsIds[i] = subId;
+            subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
+            int simState = mKeyguardUpdateMonitor.getSimState(subId);
+            CharSequence carrierName = subs.get(i).getCarrierName();
+            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+            if (DEBUG) {
+                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+            }
+            if (carrierTextForSimState != null) {
+                allSimsMissing = false;
+                carrierNames[i] = carrierTextForSimState;
+            }
+            if (simState == TelephonyManager.SIM_STATE_READY) {
+                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+                if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+                    // hack for WFC (IWLAN) not turning off immediately once
+                    // Wi-Fi is disassociated or disabled
+                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                            || (mWifiManager != null && mWifiManager.isWifiEnabled()
+                            && mWifiManager.getConnectionInfo() != null
+                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+                        if (DEBUG) {
+                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+                        }
+                        anySimReadyAndInService = true;
+                    }
+                }
+            }
+        }
+        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+        // This condition will also be true always when numSubs == 0
+        if (allSimsMissing && !anySimReadyAndInService) {
+            if (numSubs != 0) {
+                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+                // This depends on mPlmn containing the text "Emergency calls only" when the radio
+                // has some connectivity. Otherwise, it should be null or empty and just show
+                // "No SIM card"
+                // Grab the first subscripton, because they all should contain the emergency text,
+                // described above.
+                displayText = makeCarrierStringOnEmergencyCapable(
+                        getMissingSimMessage(), subs.get(0).getCarrierName());
+            } else {
+                // We don't have a SubscriptionInfo to get the emergency calls only from.
+                // Grab it from the old sticky broadcast if possible instead. We can use it
+                // here because no subscriptions are active, so we don't have
+                // to worry about MSIM clashing.
+                CharSequence text =
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
+                Intent i = getContext().registerReceiver(null,
+                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
+                if (i != null) {
+                    String spn = "";
+                    String plmn = "";
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
+                        spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
+                    }
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
+                        plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
+                    }
+                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+                    if (Objects.equals(plmn, spn)) {
+                        text = plmn;
+                    } else {
+                        text = concatenate(plmn, spn, mSeparator);
+                    }
+                }
+                displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+            }
+        }
+
+        if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
+
+        displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
+                allSimsMissing);
+
+        boolean airplaneMode = false;
+        // APM (airplane mode) != no carrier state. There are carrier services
+        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+            displayText = getAirplaneModeMessage();
+            airplaneMode = true;
+        }
+
+        final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
+                displayText,
+                carrierNames,
+                !allSimsMissing,
+                subsIds,
+                airplaneMode);
+        postToCallback(info);
+    }
+
+    @VisibleForTesting
+    protected void postToCallback(CarrierTextCallbackInfo info) {
+        final CarrierTextCallback callback = mCarrierTextCallback;
+        if (callback != null) {
+            mMainHandler.post(() -> callback.updateCarrierInfo(info));
+        }
+    }
+
+    private Context getContext() {
+        return mContext;
+    }
+
+    private String getMissingSimMessage() {
+        return mShowMissingSim && mTelephonyCapable
+                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+    }
+
+    private String getAirplaneModeMessage() {
+        return mShowAirplaneMode
+                ? getContext().getString(R.string.airplane_mode) : "";
+    }
+
+    /**
+     * Top-level function for creating carrier text. Makes text based on simState, PLMN
+     * and SPN as well as device capabilities, such as being emergency call capable.
+     *
+     * @return Carrier text if not in missing state, null otherwise.
+     */
+    private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
+        CharSequence carrierText = null;
+        CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case Normal:
+                carrierText = text;
+                break;
+
+            case SimNotReady:
+                // Null is reserved for denoting missing, in this case we have nothing to display.
+                carrierText = ""; // nothing to display yet.
+                break;
+
+            case NetworkLocked:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        mContext.getText(R.string.keyguard_network_locked_message), text);
+                break;
+
+            case SimMissing:
+                carrierText = null;
+                break;
+
+            case SimPermDisabled:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(
+                                R.string.keyguard_permanent_disabled_sim_message_short),
+                        text);
+                break;
+
+            case SimMissingLocked:
+                carrierText = null;
+                break;
+
+            case SimLocked:
+                carrierText = makeCarrierStringOnLocked(
+                        getContext().getText(R.string.keyguard_sim_locked_message),
+                        text);
+                break;
+
+            case SimPukLocked:
+                carrierText = makeCarrierStringOnLocked(
+                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
+                        text);
+                break;
+            case SimIoError:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_error_message_short),
+                        text);
+                break;
+            case SimUnknown:
+                carrierText = null;
+                break;
+        }
+
+        return carrierText;
+    }
+
+    /*
+     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+     */
+    private CharSequence makeCarrierStringOnEmergencyCapable(
+            CharSequence simMessage, CharSequence emergencyCallMessage) {
+        if (mIsEmergencyCallCapable) {
+            return concatenate(simMessage, emergencyCallMessage, mSeparator);
+        }
+        return simMessage;
+    }
+
+    /*
+     * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
+     * DSDS
+     */
+    private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
+            CharSequence carrierName) {
+        final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
+        final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
+        if (simMessageValid && carrierNameValid) {
+            return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
+                    carrierName, simMessage);
+        } else if (simMessageValid) {
+            return simMessage;
+        } else if (carrierNameValid) {
+            return carrierName;
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Determine the current status of the lock screen given the SIM state and other stuff.
+     */
+    private CarrierTextManager.StatusMode getStatusForIccState(int simState) {
+        final boolean missingAndNotProvisioned =
+                !mKeyguardUpdateMonitor.isDeviceProvisioned()
+                        && (simState == TelephonyManager.SIM_STATE_ABSENT
+                        || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
+
+        // Assume we're NETWORK_LOCKED if not provisioned
+        simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
+        switch (simState) {
+            case TelephonyManager.SIM_STATE_ABSENT:
+                return CarrierTextManager.StatusMode.SimMissing;
+            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
+                return CarrierTextManager.StatusMode.SimMissingLocked;
+            case TelephonyManager.SIM_STATE_NOT_READY:
+                return CarrierTextManager.StatusMode.SimNotReady;
+            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+                return CarrierTextManager.StatusMode.SimLocked;
+            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
+                return CarrierTextManager.StatusMode.SimPukLocked;
+            case TelephonyManager.SIM_STATE_READY:
+                return CarrierTextManager.StatusMode.Normal;
+            case TelephonyManager.SIM_STATE_PERM_DISABLED:
+                return CarrierTextManager.StatusMode.SimPermDisabled;
+            case TelephonyManager.SIM_STATE_UNKNOWN:
+                return CarrierTextManager.StatusMode.SimUnknown;
+            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
+                return CarrierTextManager.StatusMode.SimIoError;
+        }
+        return CarrierTextManager.StatusMode.SimUnknown;
+    }
+
+    private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+            CharSequence separator) {
+        final boolean plmnValid = !TextUtils.isEmpty(plmn);
+        final boolean spnValid = !TextUtils.isEmpty(spn);
+        if (plmnValid && spnValid) {
+            return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+        } else if (plmnValid) {
+            return plmn;
+        } else if (spnValid) {
+            return spn;
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
+     * separator added so there are no extra separators that are not needed.
+     */
+    private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
+        int length = sequences.length;
+        if (length == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            if (!TextUtils.isEmpty(sequences[i])) {
+                if (!TextUtils.isEmpty(sb)) {
+                    sb.append(separator);
+                }
+                sb.append(sequences[i]);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+        if (!TextUtils.isEmpty(string)) {
+            list.add(string);
+        }
+        return list;
+    }
+
+    private CharSequence getCarrierHelpTextForSimState(int simState,
+            String plmn, String spn) {
+        int carrierHelpTextId = 0;
+        CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case NetworkLocked:
+                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+                break;
+
+            case SimMissing:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+                break;
+
+            case SimPermDisabled:
+                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+                break;
+
+            case SimMissingLocked:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+                break;
+
+            case Normal:
+            case SimLocked:
+            case SimPukLocked:
+                break;
+        }
+
+        return mContext.getText(carrierHelpTextId);
+    }
+
+    /** Injectable Buildeer for {@#link CarrierTextManager}. */
+    public static class Builder {
+        private final Context mContext;
+        private final String mSeparator;
+        private final WifiManager mWifiManager;
+        private final ConnectivityManager mConnectivityManager;
+        private final TelephonyManager mTelephonyManager;
+        private final WakefulnessLifecycle mWakefulnessLifecycle;
+        private final Handler mMainHandler;
+        private final Handler mBgHandler;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private boolean mShowAirplaneMode;
+        private boolean mShowMissingSim;
+
+        @Inject
+        public Builder(Context context, @Main Resources resources,
+                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+                TelephonyManager telephonyManager, WakefulnessLifecycle wakefulnessLifecycle,
+                @Main Handler mainHandler, @Background Handler bgHandler,
+                KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            mContext = context;
+            mSeparator = resources.getString(
+                    com.android.internal.R.string.kg_text_message_separator);
+            mWifiManager = wifiManager;
+            mConnectivityManager = connectivityManager;
+            mTelephonyManager = telephonyManager;
+            mWakefulnessLifecycle = wakefulnessLifecycle;
+            mMainHandler = mainHandler;
+            mBgHandler = bgHandler;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        }
+
+        /** */
+        public Builder setShowAirplaneMode(boolean showAirplaneMode) {
+            mShowAirplaneMode = showAirplaneMode;
+            return this;
+        }
+
+        /** */
+        public Builder setShowMissingSim(boolean showMissingSim) {
+            mShowMissingSim = showMissingSim;
+            return this;
+        }
+
+        /** Create a CarrierTextManager. */
+        public CarrierTextManager build() {
+            return new CarrierTextManager(
+                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+                    mConnectivityManager, mTelephonyManager, mWakefulnessLifecycle, mMainHandler,
+                    mBgHandler, mKeyguardUpdateMonitor);
+        }
+    }
+    /**
+     * Data structure for passing information to CarrierTextController subscribers
+     */
+    public static final class CarrierTextCallbackInfo {
+        public final CharSequence carrierText;
+        public final CharSequence[] listOfCarriers;
+        public final boolean anySimReady;
+        public final int[] subscriptionIds;
+        public boolean airplaneMode;
+
+        @VisibleForTesting
+        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds) {
+            this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+        }
+
+        @VisibleForTesting
+        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+            this.carrierText = carrierText;
+            this.listOfCarriers = listOfCarriers;
+            this.anySimReady = anySimReady;
+            this.subscriptionIds = subscriptionIds;
+            this.airplaneMode = airplaneMode;
+        }
+    }
+
+    /**
+     * Callback to communicate to Views
+     */
+    public interface CarrierTextCallback {
+        /**
+         * Provides updated carrier information.
+         */
+        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+        /**
+         * Notifies the View that the device is going to sleep
+         */
+        default void startedGoingToSleep() {};
+
+        /**
+         * Notifies the View that the device finished waking up
+         */
+        default void finishedWakingUp() {};
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 707ee29..c4b02f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,34 +16,16 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.widget.Button;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.util.EmergencyDialerConstants;
 
 /**
  * This class implements a smart emergency button that updates itself based
@@ -53,34 +35,14 @@
  */
 public class EmergencyButton extends Button {
 
-    private static final String LOG_TAG = "EmergencyButton";
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
 
     private int mDownX;
     private int mDownY;
-    KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-
-        @Override
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            updateEmergencyCallButton();
-        }
-
-        @Override
-        public void onPhoneStateChanged(int phoneState) {
-            updateEmergencyCallButton();
-        }
-    };
     private boolean mLongPressWasDragged;
 
-    public interface EmergencyButtonCallback {
-        public void onEmergencyButtonClickedWhenInCall();
-    }
-
     private LockPatternUtils mLockPatternUtils;
-    private PowerManager mPowerManager;
-    private EmergencyButtonCallback mEmergencyButtonCallback;
 
-    private final boolean mIsVoiceCapable;
     private final boolean mEnableEmergencyCallWhileSimLocked;
 
     public EmergencyButton(Context context) {
@@ -89,34 +51,15 @@
 
     public EmergencyButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
         mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
     }
 
-    private TelephonyManager getTelephonyManager() {
-        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mLockPatternUtils = new LockPatternUtils(mContext);
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        setOnClickListener(v -> takeEmergencyCallAction());
         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
             setOnLongClickListener(v -> {
                 if (!mLongPressWasDragged
@@ -127,7 +70,6 @@
                 return false;
             });
         }
-        whitelistIpcs(this::updateEmergencyCallButton);
     }
 
     @Override
@@ -165,65 +107,13 @@
         return super.performLongClick();
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateEmergencyCallButton();
-    }
-
-    /**
-     * Shows the emergency dialer or returns the user to the existing call.
-     */
-    public void takeEmergencyCallAction() {
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
-        if (mPowerManager != null) {
-            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
-        }
-        try {
-            ActivityTaskManager.getService().stopSystemLockTaskMode();
-        } catch (RemoteException e) {
-            Slog.w(LOG_TAG, "Failed to stop app pinning");
-        }
-        if (isInCall()) {
-            resumeCall();
-            if (mEmergencyButtonCallback != null) {
-                mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
-            }
-        } else {
-            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-            if (updateMonitor != null) {
-                updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
-            } else {
-                Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
-            }
-            TelecomManager telecomManager = getTelecommManager();
-            if (telecomManager == null) {
-                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
-                return;
-            }
-            Intent emergencyDialIntent =
-                    telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
-                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                            .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
-                                    EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
-
-            getContext().startActivityAsUser(emergencyDialIntent,
-                    ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
-                    new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
-        }
-    }
-
-    private void updateEmergencyCallButton() {
+    void updateEmergencyCallButton(boolean isInCall, boolean isVoiceCapable, boolean simLocked) {
         boolean visible = false;
-        if (mIsVoiceCapable) {
+        if (isVoiceCapable) {
             // Emergency calling requires voice capability.
-            if (isInCall()) {
+            if (isInCall) {
                 visible = true; // always show "return to call" if phone is off-hook
             } else {
-                final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
-                        .isSimPinVoiceSecure();
                 if (simLocked) {
                     // Some countries can't handle emergency calls while SIM is locked.
                     visible = mEnableEmergencyCallWhileSimLocked;
@@ -237,7 +127,7 @@
             setVisibility(View.VISIBLE);
 
             int textId;
-            if (isInCall()) {
+            if (isInCall) {
                 textId = com.android.internal.R.string.lockscreen_return_to_call;
             } else {
                 textId = com.android.internal.R.string.lockscreen_emergency_call;
@@ -247,26 +137,4 @@
             setVisibility(View.GONE);
         }
     }
-
-    public void setCallback(EmergencyButtonCallback callback) {
-        mEmergencyButtonCallback = callback;
-    }
-
-    /**
-     * Resumes a call in progress.
-     */
-    private void resumeCall() {
-        getTelecommManager().showInCallScreen(false);
-    }
-
-    /**
-     * @return {@code true} if there is a call currently in progress.
-     */
-    private boolean isInCall() {
-        return getTelecommManager().isInCall();
-    }
-
-    private TelecomManager getTelecommManager() {
-        return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
new file mode 100644
index 0000000..4275189
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
+@KeyguardBouncerScope
+public class EmergencyButtonController extends ViewController<EmergencyButton> {
+    static final String LOG_TAG = "EmergencyButton";
+    private final ConfigurationController mConfigurationController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final TelephonyManager mTelephonyManager;
+    private final PowerManager mPowerManager;
+    private final ActivityTaskManager mActivityTaskManager;
+    private final TelecomManager mTelecomManager;
+    private final MetricsLogger mMetricsLogger;
+
+    private EmergencyButtonCallback mEmergencyButtonCallback;
+
+    private final KeyguardUpdateMonitorCallback mInfoCallback =
+            new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            updateEmergencyCallButton();
+        }
+
+        @Override
+        public void onPhoneStateChanged(int phoneState) {
+            updateEmergencyCallButton();
+        }
+    };
+
+    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onConfigChanged(Configuration newConfig) {
+            updateEmergencyCallButton();
+        }
+    };
+
+    private EmergencyButtonController(@Nullable EmergencyButton view,
+            ConfigurationController configurationController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+            PowerManager powerManager, ActivityTaskManager activityTaskManager,
+            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+        super(view);
+        mConfigurationController = configurationController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mTelephonyManager = telephonyManager;
+        mPowerManager = powerManager;
+        mActivityTaskManager = activityTaskManager;
+        mTelecomManager = telecomManager;
+        mMetricsLogger = metricsLogger;
+    }
+
+    @Override
+    protected void onInit() {
+        whitelistIpcs(this::updateEmergencyCallButton);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mConfigurationController.addCallback(mConfigurationListener);
+        mView.setOnClickListener(v -> takeEmergencyCallAction());
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+        mConfigurationController.removeCallback(mConfigurationListener);
+    }
+
+    private void updateEmergencyCallButton() {
+        if (mView != null) {
+            mView.updateEmergencyCallButton(
+                    mTelecomManager != null && mTelecomManager.isInCall(),
+                    mTelephonyManager.isVoiceCapable(),
+                    mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+        }
+    }
+
+    public void setEmergencyButtonCallback(EmergencyButtonCallback callback) {
+        mEmergencyButtonCallback = callback;
+    }
+    /**
+     * Shows the emergency dialer or returns the user to the existing call.
+     */
+    public void takeEmergencyCallAction() {
+        mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
+        if (mPowerManager != null) {
+            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        }
+        mActivityTaskManager.stopSystemLockTaskMode();
+        if (mTelecomManager != null && mTelecomManager.isInCall()) {
+            mTelecomManager.showInCallScreen(false);
+            if (mEmergencyButtonCallback != null) {
+                mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+            }
+        } else {
+            mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+            if (mTelecomManager == null) {
+                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                return;
+            }
+            Intent emergencyDialIntent =
+                    mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                            .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+                                    EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+
+            getContext().startActivityAsUser(emergencyDialIntent,
+                    ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+                    new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+        }
+    }
+
+    /** */
+    public interface EmergencyButtonCallback {
+        /** */
+        void onEmergencyButtonClickedWhenInCall();
+    }
+
+    /** Injectable Factory for creating {@link EmergencyButtonController}. */
+    public static class Factory {
+        private final ConfigurationController mConfigurationController;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final TelephonyManager mTelephonyManager;
+        private final PowerManager mPowerManager;
+        private final ActivityTaskManager mActivityTaskManager;
+        @Nullable
+        private final TelecomManager mTelecomManager;
+        private final MetricsLogger mMetricsLogger;
+
+        @Inject
+        public Factory(ConfigurationController configurationController,
+                KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+                PowerManager powerManager, ActivityTaskManager activityTaskManager,
+                @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+
+            mConfigurationController = configurationController;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mTelephonyManager = telephonyManager;
+            mPowerManager = powerManager;
+            mActivityTaskManager = activityTaskManager;
+            mTelecomManager = telecomManager;
+            mMetricsLogger = metricsLogger;
+        }
+
+        /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
+        public EmergencyButtonController create(EmergencyButton view) {
+            return new EmergencyButtonController(view, mConfigurationController,
+                    mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
+                    mTelecomManager, mMetricsLogger);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 5760565..7a05a17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -31,7 +31,7 @@
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
@@ -41,6 +41,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
+    private final EmergencyButtonController mEmergencyButtonController;
     private CountDownTimer mCountdownTimer;
     protected KeyguardMessageAreaController mMessageAreaController;
     private boolean mDismissing;
@@ -70,11 +71,12 @@
             LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker) {
-        super(view, securityMode, keyguardSecurityCallback);
+            LatencyTracker latencyTracker, EmergencyButtonController emergencyButtonController) {
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
+        mEmergencyButtonController = emergencyButtonController;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = messageAreaControllerFactory.create(kma);
     }
@@ -83,6 +85,7 @@
 
     @Override
     public void onInit() {
+        super.onInit();
         mMessageAreaController.init();
     }
 
@@ -91,10 +94,7 @@
         super.onViewAttached();
         mView.setKeyDownListener(mKeyDownListener);
         mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(mEmergencyButtonCallback);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 276036c..76a7473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -46,12 +45,15 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
 
     private MediaRouter mMediaRouter = null;
     private final DisplayManager mDisplayService;
+    private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
 
@@ -85,9 +87,11 @@
 
     @Inject
     public KeyguardDisplayManager(Context context,
+            Lazy<NavigationBarController> navigationBarControllerLazy,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             @UiBackground Executor uiBgExecutor) {
         mContext = context;
+        mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
         mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -240,7 +244,7 @@
         // Leave this task to {@link StatusBarKeyguardViewManager}
         if (displayId == DEFAULT_DISPLAY) return;
 
-        NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+        NavigationBarView navBarView = mNavigationBarControllerLazy.get()
                 .getNavigationBarView(displayId);
         // We may not have nav bar on a display.
         if (navBarView == null) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 1c691e7..538daaf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -41,6 +41,7 @@
     private final SecurityMode mSecurityMode;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final EmergencyButton mEmergencyButton;
+    private final EmergencyButtonController mEmergencyButtonController;
     private boolean mPaused;
 
 
@@ -68,11 +69,18 @@
     };
 
     protected KeyguardInputViewController(T view, SecurityMode securityMode,
-            KeyguardSecurityCallback keyguardSecurityCallback) {
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            EmergencyButtonController emergencyButtonController) {
         super(view);
         mSecurityMode = securityMode;
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
+        mEmergencyButtonController = emergencyButtonController;
+    }
+
+    @Override
+    protected void onInit() {
+        mEmergencyButtonController.init();
     }
 
     @Override
@@ -154,8 +162,9 @@
         private final InputMethodManager mInputMethodManager;
         private final DelayableExecutor mMainExecutor;
         private final Resources mResources;
-        private LiftToActivateListener mLiftToActivateListener;
-        private TelephonyManager mTelephonyManager;
+        private final LiftToActivateListener mLiftToActivateListener;
+        private final TelephonyManager mTelephonyManager;
+        private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
         private final FalsingCollector mFalsingCollector;
 
         @Inject
@@ -165,7 +174,9 @@
                 KeyguardMessageAreaController.Factory messageAreaControllerFactory,
                 InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
                 @Main Resources resources, LiftToActivateListener liftToActivateListener,
-                TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+                TelephonyManager telephonyManager,
+                EmergencyButtonController.Factory emergencyButtonControllerFactory,
+                FalsingCollector falsingCollector) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -175,36 +186,47 @@
             mResources = resources;
             mLiftToActivateListener = liftToActivateListener;
             mTelephonyManager = telephonyManager;
+            mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
             mFalsingCollector = falsingCollector;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
         public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
                 SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+            EmergencyButtonController emergencyButtonController =
+                    mEmergencyButtonControllerFactory.create(
+                            keyguardInputView.findViewById(R.id.emergency_call_button));
+
             if (keyguardInputView instanceof KeyguardPatternView) {
                 return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
-                        keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+                        keyguardSecurityCallback, mLatencyTracker,
+                        emergencyButtonController,
+                        mMessageAreaControllerFactory);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mInputMethodManager, mMainExecutor, mResources);
+                        mInputMethodManager, emergencyButtonController, mMainExecutor, mResources);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mFalsingCollector);
+                        mLiftToActivateListener, emergencyButtonController, mFalsingCollector);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+                        mLiftToActivateListener, mTelephonyManager,
+                        emergencyButtonController,
+                        mFalsingCollector);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+                        mLiftToActivateListener, mTelephonyManager,
+                        emergencyButtonController,
+                        mFalsingCollector);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0f1c3c8..2e45545 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -111,10 +111,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             InputMethodManager inputMethodManager,
+            EmergencyButtonController emergencyButtonController,
             @Main DelayableExecutor mainExecutor,
             @Main Resources resources) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker);
+                messageAreaControllerFactory, latencyTracker, emergencyButtonController);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mMainExecutor = mainExecutor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 2aaf748..55e348c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -31,7 +31,7 @@
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockPatternView.Cell;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -50,6 +50,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
+    private final EmergencyButtonController mEmergencyButtonController;
     private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
 
     private KeyguardMessageAreaController mMessageAreaController;
@@ -179,11 +180,13 @@
             LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             LatencyTracker latencyTracker,
+            EmergencyButtonController emergencyButtonController,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
-        super(view, securityMode, keyguardSecurityCallback);
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
+        mEmergencyButtonController = emergencyButtonController;
         mMessageAreaControllerFactory = messageAreaControllerFactory;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = mMessageAreaControllerFactory.create(kma);
@@ -205,11 +208,7 @@
                 KeyguardUpdateMonitor.getCurrentUser()));
         // vibrate mode will be the same for the life of this screen
         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
-
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(mEmergencyButtonCallback);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
 
         View cancelBtn = mView.findViewById(R.id.cancel_button);
         if (cancelBtn != null) {
@@ -224,10 +223,7 @@
     protected void onViewDetached() {
         super.onViewDetached();
         mLockPatternView.setOnPatternListener(null);
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(null);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(null);
         View cancelBtn = mView.findViewById(R.id.cancel_button);
         if (cancelBtn != null) {
             cancelBtn.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f247948..1b5aa45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -71,9 +71,10 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker);
+                messageAreaControllerFactory, latencyTracker, emergencyButtonController);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index c0aa2af..262bed3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,10 +34,11 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,7 +23,6 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -49,24 +48,27 @@
     private final boolean mIsPukScreenAvailable;
 
     private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Inject
-    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mIsPukScreenAvailable = resources.getBoolean(
                 com.android.internal.R.bool.config_enable_puk_unlock_screen);
         mLockPatternUtils = lockPatternUtils;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
     public SecurityMode getSecurityMode(int userId) {
-        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
         if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
             return SecurityMode.SimPin;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
     private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
             new ArrayList<>();
     private final LayoutInflater mLayoutInflater;
+    private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
     private final Factory mKeyguardSecurityViewControllerFactory;
 
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
-            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
+            EmergencyButtonController.Factory emergencyButtonControllerFactory) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
+        mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
     }
 
     @Override
@@ -111,7 +114,8 @@
 
         if (childController == null) {
             childController = new NullKeyguardInputViewController(
-                    securityMode, keyguardSecurityCallback);
+                    securityMode, keyguardSecurityCallback,
+                    mEmergencyButtonControllerFactory.create(null));
         }
 
         return childController;
@@ -140,8 +144,9 @@
     private static class NullKeyguardInputViewController
             extends KeyguardInputViewController<KeyguardInputView> {
         protected NullKeyguardInputViewController(SecurityMode securityMode,
-                KeyguardSecurityCallback keyguardSecurityCallback) {
-            super(null, securityMode, keyguardSecurityCallback);
+                KeyguardSecurityCallback keyguardSecurityCallback,
+                EmergencyButtonController emergencyButtonController) {
+            super(null, securityMode, keyguardSecurityCallback, emergencyButtonController);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index b218141..a5a3a80 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -78,10 +78,11 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 890a17c..b584bf7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 
@@ -85,10 +84,11 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -196,8 +196,7 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
         } else {
-            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
-                    .getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
             if (info != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index fb97a30..1fbf71d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.FileDescriptor;
@@ -317,6 +315,22 @@
                 R.dimen.widget_label_font_size);
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
+
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged();
+            }
+        }
+    }
+
+    void onOverlayChanged() {
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onOverlayChanged();
+            }
+        }
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -479,8 +493,7 @@
      * Representation of an item that appears under the clock on main keyguard message.
      */
     @VisibleForTesting
-    static class KeyguardSliceTextView extends TextView implements
-            ConfigurationController.ConfigurationListener {
+    static class KeyguardSliceTextView extends TextView {
         private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
         @StyleRes
@@ -492,24 +505,10 @@
             setEllipsize(TruncateAt.END);
         }
 
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-            Dependency.get(ConfigurationController.class).addCallback(this);
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-            Dependency.get(ConfigurationController.class).removeCallback(this);
-        }
-
-        @Override
         public void onDensityOrFontScaleChanged() {
             updatePadding();
         }
 
-        @Override
         public void onOverlayChanged() {
             setTextAppearance(sStyleId);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
         public void onDensityOrFontScaleChanged() {
             mView.onDensityOrFontScaleChanged();
         }
+        @Override
+        public void onOverlayChanged() {
+            mView.onOverlayChanged();
+        }
     };
 
     Observer<Slice> mObserver = new Observer<Slice>() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 import java.io.FileDescriptor;
@@ -56,7 +55,6 @@
     private final IActivityManager mIActivityManager;
 
     private TextView mLogoutView;
-    private boolean mCanShowLogout = true; // by default, try to show the logout button here
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
     private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
@@ -130,11 +128,6 @@
         }
     }
 
-    void setCanShowLogout(boolean canShowLogout) {
-        mCanShowLogout = canShowLogout;
-        updateLogoutView();
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -159,10 +152,7 @@
         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
         onSliceContentChanged();
 
-        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setEnableMarquee(shouldMarquee);
         updateOwnerInfo();
-        updateLogoutView();
         updateDark();
     }
 
@@ -209,11 +199,11 @@
         return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
     }
 
-    void updateLogoutView() {
+    void updateLogoutView(boolean shouldShowLogout) {
         if (mLogoutView == null) {
             return;
         }
-        mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE);
+        mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
         // Logout button will stay in language of user 0 if we don't set that manually.
         mLogoutView.setText(mContext.getResources().getString(
                 com.android.internal.R.string.global_action_logout));
@@ -313,11 +303,6 @@
         }
     }
 
-    private boolean shouldShowLogout() {
-        return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
-                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-    }
-
     private void onLogoutClicked(View view) {
         int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
         try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6fb6760..bfe7f8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.View;
 
@@ -78,6 +79,8 @@
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+        mView.updateLogoutView(shouldShowLogout());
     }
 
     @Override
@@ -245,6 +248,11 @@
         }
     }
 
+    private boolean shouldShowLogout() {
+        return mKeyguardUpdateMonitor.isLogoutEnabled()
+                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+    }
+
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
@@ -271,12 +279,12 @@
                 mKeyguardSliceViewController.updateTopMargin(
                         mKeyguardClockSwitchController.getClockTextTopPadding());
                 mView.setCanShowOwnerInfo(false);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             } else {
                 // reset margin
                 mKeyguardSliceViewController.updateTopMargin(0);
                 mView.setCanShowOwnerInfo(true);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             }
             updateAodIcons();
         }
@@ -302,7 +310,7 @@
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
                 refreshTime();
                 mView.updateOwnerInfo();
-                mView.updateLogoutView();
+                mView.updateLogoutView(shouldShowLogout());
             }
         }
 
@@ -320,12 +328,12 @@
         public void onUserSwitchComplete(int userId) {
             mKeyguardClockSwitchController.refreshFormat();
             mView.updateOwnerInfo();
-            mView.updateLogoutView();
+            mView.updateLogoutView(shouldShowLogout());
         }
 
         @Override
         public void onLogoutEnabledChanged() {
-            mView.updateLogoutView();
+            mView.updateLogoutView(shouldShowLogout());
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
new file mode 100644
index 0000000..c4be1ba535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.clock;
+
+import java.util.List;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clock package. */
+@Module
+public abstract class ClockModule {
+
+    /** */
+    @Provides
+    public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
+        return clockManager.getClockInfos();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
index 5ef35be..b6413cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
@@ -28,11 +28,12 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 
 import java.io.FileNotFoundException;
 import java.util.List;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Exposes custom clock face options and provides realistic preview images.
@@ -65,15 +66,12 @@
     private static final String CONTENT_SCHEME = "content";
     private static final String AUTHORITY = "com.android.keyguard.clock";
 
-    private final Supplier<List<ClockInfo>> mClocksSupplier;
-
-    public ClockOptionsProvider() {
-        this(() -> Dependency.get(ClockManager.class).getClockInfos());
-    }
+    @Inject
+    public Provider<List<ClockInfo>> mClockInfosProvider;
 
     @VisibleForTesting
-    ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) {
-        mClocksSupplier = clocksSupplier;
+    ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
+        mClockInfosProvider = clockInfosProvider;
     }
 
     @Override
@@ -99,7 +97,7 @@
         }
         MatrixCursor cursor = new MatrixCursor(new String[] {
                 COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
-        List<ClockInfo> clocks = mClocksSupplier.get();
+        List<ClockInfo> clocks = mClockInfosProvider.get();
         for (int i = 0; i < clocks.size(); i++) {
             ClockInfo clock = clocks.get(i);
             cursor.newRow()
@@ -139,7 +137,7 @@
             throw new FileNotFoundException("Invalid preview url, missing id");
         }
         ClockInfo clock = null;
-        List<ClockInfo> clocks = mClocksSupplier.get();
+        List<ClockInfo> clocks = mClockInfosProvider.get();
         for (int i = 0; i < clocks.size(); i++) {
             if (id.equals(clocks.get(i).getId())) {
                 clock = clocks.get(i);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
new file mode 100644
index 0000000..3a0357d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardQsUserSwitch and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardQsUserSwitchComponent {
+    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardQsUserSwitchComponent build(
+                @BindsInstance UserAvatarView userAvatarView);
+    }
+
+    /** Builds a {@link KeyguardQsUserSwitchController}. */
+    KeyguardQsUserSwitchController getKeyguardQsUserSwitchController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
new file mode 100644
index 0000000..49a617e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.keyguard.KeyguardStatusViewController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusViewComponent}
+ */
+@Subcomponent(modules = {KeyguardStatusBarViewModule.class})
+@KeyguardStatusBarViewScope
+public interface KeyguardStatusBarViewComponent {
+    /** Simple factory for {@link KeyguardStatusBarViewComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view);
+    }
+
+    /** Builds a {@link KeyguardStatusViewController}. */
+    KeyguardStatusBarViewController getKeyguardStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
new file mode 100644
index 0000000..a672523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -0,0 +1,34 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.keyguard.CarrierText;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusBarViewComponent}. */
+@Module
+public abstract class KeyguardStatusBarViewModule {
+    @Provides
+    @KeyguardStatusBarViewScope
+    static CarrierText getCarrierText(KeyguardStatusBarView view) {
+        return view.findViewById(R.id.keyguard_carrier_text);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
new file mode 100644
index 0000000..ba0642f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardStatusBarViewScope {}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
index 1b6476c..d342377 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -25,6 +25,8 @@
 
 /**
  * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusBarViewComponent}
  */
 @Subcomponent(modules = {KeyguardStatusViewModule.class})
 @KeyguardStatusViewScope
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ffb8446..91c2dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
@@ -146,4 +147,9 @@
      * Member injection into the supplied argument.
      */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(ClockOptionsProvider clockOptionsProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b0067cd..b67db03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -22,6 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.clock.ClockModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
@@ -90,6 +91,7 @@
 @Module(includes = {
             AppOpsModule.class,
             AssistModule.class,
+            ClockModule.class,
             ControlsModule.class,
             DemoModeModule.class,
             FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 9e5b225..a747edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -29,6 +29,8 @@
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -62,7 +64,11 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class},
+@Module(subcomponents = {
+        KeyguardQsUserSwitchComponent.class,
+        KeyguardStatusBarViewComponent.class,
+        KeyguardStatusViewComponent.class,
+        KeyguardUserSwitcherComponent.class},
         includes = {FalsingModule.class})
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index e8dba8f..c1db8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.log
 
 import android.util.Log
-import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.dagger.LogModule
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
@@ -58,7 +57,7 @@
  * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
  * the first letter of any of the previous.
  *
- * Buffers are provided by [LogModule].
+ * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
  *
  * @param name The name of this buffer
  * @param maxLogs The maximum number of messages to keep in memory at any one time, including the
@@ -77,10 +76,6 @@
     var frozen = false
         private set
 
-    fun attach(dumpManager: DumpManager) {
-        dumpManager.registerBuffer(name, this)
-    }
-
     /**
      * Logs a message to the log buffer
      *
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
new file mode 100644
index 0000000..0622df3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import javax.inject.Inject
+
+@SysUISingleton
+class LogBufferFactory @Inject constructor(
+    private val dumpManager: DumpManager,
+    private val logcatEchoTracker: LogcatEchoTracker
+) {
+    @JvmOverloads
+    fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer {
+        val buffer = LogBuffer(name, maxPoolSize, flexSize, logcatEchoTracker)
+        dumpManager.registerBuffer(name, buffer)
+        return buffer
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index fff185b..19193f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,8 +22,8 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.log.LogcatEchoTrackerDebug;
 import com.android.systemui.log.LogcatEchoTrackerProd;
@@ -40,96 +40,64 @@
     @Provides
     @SysUISingleton
     @DozeLog
-    public static LogBuffer provideDozeLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
+        return factory.create("DozeLog", 100);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
     @NotificationLog
-    public static LogBuffer provideNotificationsLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifLog", 1000, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifLog", 1000);
     }
 
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
     @NotificationSectionLog
-    public static LogBuffer provideNotificationSectionLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifSectionLog", 1000);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
     @NotifInteractionLog
-    public static LogBuffer provideNotifInteractionLogBuffer(
-            LogcatEchoTracker echoTracker,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotifInteractionLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifInteractionLog", 50);
     }
 
     /** Provides a logging buffer for all logs related to Quick Settings. */
     @Provides
     @SysUISingleton
     @QSLog
-    public static LogBuffer provideQuickSettingsLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
+        return factory.create("QSLog", 500);
     }
 
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
     @SysUISingleton
     @BroadcastDispatcherLog
-    public static LogBuffer provideBroadcastDispatcherLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
+        return factory.create("BroadcastDispatcherLog", 500);
     }
 
     /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
     @Provides
     @SysUISingleton
     @ToastLog
-    public static LogBuffer provideToastLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideToastLogBuffer(LogBufferFactory factory) {
+        return factory.create("ToastLog", 50);
     }
 
     /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
     @Provides
     @SysUISingleton
     @PrivacyLog
-    public static LogBuffer providePrivacyLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer providePrivacyLogBuffer(LogBufferFactory factory) {
+        return factory.create("PrivacyLog", 100);
     }
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 9353526..a3ff375 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,7 +1,9 @@
 package com.android.systemui.media
 
+import android.animation.ArgbEvaluator
 import android.content.Context
 import android.content.Intent
+import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.util.Log
@@ -112,6 +114,9 @@
     private val visualStabilityCallback: VisualStabilityManager.Callback
     private var needsReordering: Boolean = false
     private var keysNeedRemoval = mutableSetOf<String>()
+    private var bgColor = getBackgroundColor()
+    private var fgColor = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.textColorPrimary).defaultColor
     private var isRtl: Boolean = false
         set(value) {
             if (value != field) {
@@ -147,7 +152,7 @@
         }
 
         override fun onUiModeChanged() {
-            // Only settings button needs to update for dark theme
+            recreatePlayers()
             inflateSettingsButton()
         }
     }
@@ -249,6 +254,11 @@
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
+        data.actions.forEach {
+            it.icon?.setTintList(ColorStateList.valueOf(fgColor))
+        }
+        data.appIcon?.setTintList(ColorStateList.valueOf(fgColor))
+        val dataCopy = data.copy(backgroundColor = bgColor)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
         if (existingPlayer == null) {
             var newPlayer = mediaControlPanelFactory.get()
@@ -257,14 +267,14 @@
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
             newPlayer.view?.player?.setLayoutParams(lp)
-            newPlayer.bind(data, key)
+            newPlayer.bind(dataCopy, key)
             newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(key, data, newPlayer)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers()
         } else {
-            existingPlayer.bind(data, key)
-            MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+            existingPlayer.bind(dataCopy, key)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer)
             if (visualStabilityManager.isReorderingAllowed) {
                 reorderAllPlayers()
             } else {
@@ -298,12 +308,27 @@
     }
 
     private fun recreatePlayers() {
+        bgColor = getBackgroundColor()
+
+        fgColor = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.textColorPrimary).defaultColor
+        pageIndicator.tintList = ColorStateList.valueOf(fgColor)
+
         MediaPlayerData.mediaData().forEach { (key, data) ->
             removePlayer(key, dismissMediaData = false)
             addOrUpdatePlayer(key = key, oldKey = null, data = data)
         }
     }
 
+    private fun getBackgroundColor(): Int {
+        val themeAccent = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.colorAccent).defaultColor
+        val themeBackground = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.colorBackground).defaultColor
+        // Simulate transparency - cannot be actually transparent because of lockscreen
+        return ArgbEvaluator().evaluate(0.25f, themeBackground, themeAccent) as Int
+    }
+
     private fun updatePageIndicator() {
         val numPages = mediaContent.getChildCount()
         pageIndicator.setNumPages(numPages)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3629d4d..55c55b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -25,7 +25,6 @@
 import android.content.IntentFilter
 import android.graphics.Bitmap
 import android.graphics.Canvas
-import android.graphics.Color
 import android.graphics.ImageDecoder
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
@@ -38,7 +37,6 @@
 import android.service.notification.StatusBarNotification
 import android.text.TextUtils
 import android.util.Log
-import com.android.internal.graphics.ColorUtils
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -48,7 +46,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
@@ -68,10 +65,6 @@
 
 private const val TAG = "MediaDataManager"
 private const val DEBUG = true
-private const val DEFAULT_LUMINOSITY = 0.25f
-private const val LUMINOSITY_THRESHOLD = 0.05f
-private const val SATURATION_MULTIPLIER = 0.8f
-const val DEFAULT_COLOR = Color.DKGRAY
 
 private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
         emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@@ -110,6 +103,11 @@
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
+    private val themeText = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.textColorPrimary).defaultColor
+    private val bgColor = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.colorBackground).defaultColor
+
     // Internal listeners are part of the internal pipeline. External listeners (those registered
     // with [MediaDeviceManager.addListener]) receive events after they have propagated through
     // the internal pipeline.
@@ -395,7 +393,6 @@
         } else {
             null
         }
-        val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR
 
         val mediaAction = getResumeMediaAction(resumeAction)
         foregroundExecutor.execute {
@@ -449,7 +446,6 @@
                 }
             }
         }
-        val bgColor = computeBackgroundColor(artworkBitmap)
 
         // App name
         val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -506,7 +502,7 @@
                     Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
                 } else {
                     action.getIcon()
-                }
+                }.setTint(themeText)
                 val mediaAction = MediaAction(
                         mediaActionIcon,
                         runnable,
@@ -589,38 +585,9 @@
         }
     }
 
-    private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int {
-        var color = Color.WHITE
-        if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) {
-            // If we have valid art, get colors from that
-            val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
-                    .generate()
-            val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
-            color = swatch.rgb
-        } else {
-            return DEFAULT_COLOR
-        }
-        // Adapt background color, so it's always subdued and text is legible
-        val tmpHsl = floatArrayOf(0f, 0f, 0f)
-        ColorUtils.colorToHSL(color, tmpHsl)
-
-        val l = tmpHsl[2]
-        // Colors with very low luminosity can have any saturation. This means that changing the
-        // luminosity can make a black become red. Let's remove the saturation of very light or
-        // very dark colors to avoid this issue.
-        if (l < LUMINOSITY_THRESHOLD || l > 1f - LUMINOSITY_THRESHOLD) {
-            tmpHsl[1] = 0f
-        }
-        tmpHsl[1] *= SATURATION_MULTIPLIER
-        tmpHsl[2] = DEFAULT_LUMINOSITY
-
-        color = ColorUtils.HSLToColor(tmpHsl)
-        return color
-    }
-
     private fun getResumeMediaAction(action: Runnable): MediaAction {
         return MediaAction(
-            Icon.createWithResource(context, R.drawable.lb_ic_play),
+            Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
             action,
             context.getString(R.string.controls_media_resume)
         )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 619729e..9967936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -72,6 +72,7 @@
     private boolean mFullyExpanded;
     private QuickStatusBarHeader mHeader;
     private boolean mTriggeredExpand;
+    private boolean mShouldAnimate;
     private int mOpenX;
     private int mOpenY;
     private boolean mAnimatingOpen;
@@ -108,16 +109,6 @@
         updateDetailText();
 
         mClipper = new QSDetailClipper(this);
-
-        final OnClickListener doneListener = new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                announceForAccessibility(
-                        mContext.getString(R.string.accessibility_desc_quick_settings));
-                mQsPanelController.closeDetail();
-            }
-        };
-        mDetailDoneButton.setOnClickListener(doneListener);
     }
 
     /** */
@@ -169,6 +160,7 @@
     public void handleShowingDetail(final DetailAdapter adapter, int x, int y,
             boolean toggleQs) {
         final boolean showingDetail = adapter != null;
+        final boolean wasShowingDetail = mDetailAdapter != null;
         setClickable(showingDetail);
         if (showingDetail) {
             setupDetailHeader(adapter);
@@ -178,6 +170,7 @@
             } else {
                 mTriggeredExpand = false;
             }
+            mShouldAnimate = adapter.shouldAnimate();
             mOpenX = x;
             mOpenY = y;
         } else {
@@ -190,10 +183,10 @@
             }
         }
 
-        boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
-        if (!visibleDiff && mDetailAdapter == adapter) return;  // already in right state
-        AnimatorListener listener = null;
-        if (adapter != null) {
+        boolean visibleDiff = wasShowingDetail != showingDetail;
+        if (!visibleDiff && !wasShowingDetail) return;  // already in right state
+        AnimatorListener listener;
+        if (showingDetail) {
             int viewCacheIndex = adapter.getMetricsCategory();
             View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
                     mDetailContent);
@@ -213,7 +206,7 @@
             listener = mHideGridContentWhenDone;
             setVisibility(View.VISIBLE);
         } else {
-            if (mDetailAdapter != null) {
+            if (wasShowingDetail) {
                 Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
                 mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
             }
@@ -227,7 +220,15 @@
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
 
-        animateDetailVisibleDiff(x, y, visibleDiff, listener);
+        if (mShouldAnimate) {
+            animateDetailVisibleDiff(x, y, visibleDiff, listener);
+        } else {
+            if (showingDetail) {
+                showImmediately();
+            } else {
+                hideImmediately();
+            }
+        }
     }
 
     protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
@@ -245,6 +246,17 @@
         }
     }
 
+    void showImmediately() {
+        setVisibility(VISIBLE);
+        mClipper.cancelAnimator();
+        mClipper.showBackground();
+    }
+
+    public void hideImmediately() {
+        mClipper.cancelAnimator();
+        setVisibility(View.GONE);
+    }
+
     protected void setupDetailFooter(DetailAdapter adapter) {
         final Intent settingsIntent = adapter.getSettingsIntent();
         mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
@@ -255,6 +267,13 @@
             Dependency.get(ActivityStarter.class)
                     .postStartActivityDismissingKeyguard(settingsIntent, 0);
         });
+        mDetailDoneButton.setOnClickListener(v -> {
+            announceForAccessibility(
+                    mContext.getString(R.string.accessibility_desc_quick_settings));
+            if (!adapter.onDoneButtonClicked()) {
+                mQsPanelController.closeDetail();
+            }
+        });
     }
 
     protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
index 7d87e174..b50af00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
@@ -39,7 +39,7 @@
     /** Show the supplied DetailAdapter in the Quick Settings. */
     public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
         if (mQsPanelController != null) {
-            mQsPanelController.showDetailDapater(detailAdapter, x, y);
+            mQsPanelController.showDetailAdapter(detailAdapter, x, y);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 7820921..fcb35e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -311,7 +311,7 @@
     }
 
     /** */
-    public void showDetailDapater(DetailAdapter detailAdapter, int x, int y) {
+    public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
         mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index a567f51..aa6bbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -34,7 +34,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
 import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.R;
@@ -58,7 +58,7 @@
     private final ActivityStarter mActivityStarter;
     private final Handler mBgHandler;
     private final NetworkController mNetworkController;
-    private final CarrierTextController mCarrierTextController;
+    private final CarrierTextManager mCarrierTextManager;
     private final TextView mNoSimTextView;
     private final H mMainHandler;
     private final Callback mCallback;
@@ -153,7 +153,7 @@
                 }
             };
 
-    private static class Callback implements CarrierTextController.CarrierTextCallback {
+    private static class Callback implements CarrierTextManager.CarrierTextCallback {
         private H mHandler;
 
         Callback(H handler) {
@@ -161,7 +161,7 @@
         }
 
         @Override
-        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
             mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
         }
     }
@@ -169,7 +169,7 @@
     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
-            CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+            CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
         if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
             mProviderModel = true;
         } else {
@@ -178,7 +178,7 @@
         mActivityStarter = activityStarter;
         mBgHandler = bgHandler;
         mNetworkController = networkController;
-        mCarrierTextController = carrierTextControllerBuilder
+        mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
                 .build();
@@ -196,7 +196,6 @@
         mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
         mCallback = new Callback(mMainHandler);
 
-
         mCarrierGroups[0] = view.getCarrier1View();
         mCarrierGroups[1] = view.getCarrier2View();
         mCarrierGroups[2] = view.getCarrier3View();
@@ -247,10 +246,10 @@
             if (mNetworkController.hasVoiceCallingFeature()) {
                 mNetworkController.addCallback(mSignalCallback);
             }
-            mCarrierTextController.setListening(mCallback);
+            mCarrierTextManager.setListening(mCallback);
         } else {
             mNetworkController.removeCallback(mSignalCallback);
-            mCarrierTextController.setListening(null);
+            mCarrierTextManager.setListening(null);
         }
     }
 
@@ -277,7 +276,7 @@
     }
 
     @MainThread
-    private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+    private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
         if (!mMainHandler.getLooper().isCurrentThread()) {
             mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
             return;
@@ -331,13 +330,13 @@
     }
 
     private static class H extends Handler {
-        private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+        private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
         private Runnable mUpdateState;
         static final int MSG_UPDATE_CARRIER_INFO = 0;
         static final int MSG_UPDATE_STATE = 1;
 
         H(Looper looper,
-                Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+                Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo,
                 Runnable updateState) {
             super(looper);
             mUpdateCarrierInfo = updateCarrierInfo;
@@ -349,7 +348,7 @@
             switch (msg.what) {
                 case MSG_UPDATE_CARRIER_INFO:
                     mUpdateCarrierInfo.accept(
-                            (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+                            (CarrierTextManager.CarrierTextCallbackInfo) msg.obj);
                     break;
                 case MSG_UPDATE_STATE:
                     mUpdateState.run();
@@ -366,13 +365,13 @@
         private final Handler mHandler;
         private final Looper mLooper;
         private final NetworkController mNetworkController;
-        private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+        private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
         private final Context mContext;
 
         @Inject
         public Builder(ActivityStarter activityStarter, @Background Handler handler,
                 @Main Looper looper, NetworkController networkController,
-                CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+                CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
             mActivityStarter = activityStarter;
             mHandler = handler;
             mLooper = looper;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 191b85b..0abff77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -469,9 +469,8 @@
             if (wifiConnected) {
                 minimalStateDescription.append(cb.mWifiSignalContentDescription);
                 minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
-                if (!TextUtils.isEmpty(state.secondaryLabel)) {
-                    minimalContentDescription.append(",").append(state.secondaryLabel);
-                }
+            } else if (!TextUtils.isEmpty(state.secondaryLabel)) {
+                minimalContentDescription.append(",").append(state.secondaryLabel);
             }
         }
         state.stateDescription = minimalStateDescription.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 26adfdc..a6cddd3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -82,7 +82,7 @@
 
     @Override
     public DetailAdapter getDetailAdapter() {
-        return mUserSwitcherController.userDetailAdapter;
+        return mUserSwitcherController.mUserDetailAdapter;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 6d64049..d97f644 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.screenshot;
 
-import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-
 import android.annotation.IdRes;
 import android.annotation.UiThread;
 import android.content.ComponentName;
@@ -29,23 +27,12 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.ActionMode;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.SearchEvent;
 import android.view.View;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
@@ -136,10 +123,8 @@
         mCallback = callback;
 
         setContentView(R.layout.long_screenshot);
-        mWindow.setCallback(new WindowCallbacks(this::doFinish));
         mWindow.getDecorView().getViewTreeObserver()
                 .addOnComputeInternalInsetsListener(this);
-
         mPreview = findViewById(R.id.preview);
 
         mSave = findViewById(R.id.save);
@@ -349,130 +334,4 @@
             mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
         }
     }
-
-    private static class WindowCallbacks implements Window.Callback {
-
-        private final Runnable mFinish;
-
-        WindowCallbacks(Runnable finish) {
-            mFinish = finish;
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-                if (DEBUG_INPUT) {
-                    Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
-                }
-                mFinish.run();
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean dispatchTrackballEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean dispatchGenericMotionEvent(MotionEvent event) {
-            return false;
-        }
-
-        @Override
-        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-            return false;
-        }
-
-        @Nullable
-        @Override
-        public View onCreatePanelView(int featureId) {
-            return null;
-        }
-
-        @Override
-        public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
-            return false;
-        }
-
-        @Override
-        public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
-            return false;
-        }
-
-        @Override
-        public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
-            return false;
-        }
-
-        @Override
-        public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
-            return false;
-        }
-
-        @Override
-        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
-        }
-
-        @Override
-        public void onContentChanged() {
-        }
-
-        @Override
-        public void onWindowFocusChanged(boolean hasFocus) {
-        }
-
-        @Override
-        public void onAttachedToWindow() {
-        }
-
-        @Override
-        public void onDetachedFromWindow() {
-        }
-
-        @Override
-        public void onPanelClosed(int featureId, @NonNull Menu menu) {
-        }
-
-        @Override
-        public boolean onSearchRequested() {
-            return false;
-        }
-
-        @Override
-        public boolean onSearchRequested(SearchEvent searchEvent) {
-            return false;
-        }
-
-        @Nullable
-        @Override
-        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
-            return null;
-        }
-
-        @Nullable
-        @Override
-        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
-            return null;
-        }
-
-        @Override
-        public void onActionModeStarted(ActionMode mode) {
-        }
-
-        @Override
-        public void onActionModeFinished(ActionMode mode) {
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 7aa41e4..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -67,6 +67,10 @@
         return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
     }
 
+    public boolean useNewLockscreenAnimations() {
+        return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
+    }
+
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2f0f90d..c1feaca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,14 +11,10 @@
 import android.graphics.PorterDuffXfermode
 import android.graphics.RadialGradient
 import android.graphics.Shader
-import android.os.SystemProperties
 import android.util.AttributeSet
 import android.view.View
 import com.android.systemui.Interpolators
 
-val enableLightReveal =
-        SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false)
-
 /**
  * Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
  * 100% of the view(s) underneath the scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 8c2fa33..85d8df8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -54,6 +55,7 @@
     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
     private final Resources mResources;
     private final BatteryController mBatteryController;
+    private final FeatureFlags mFeatureFlags;
 
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
@@ -65,7 +67,8 @@
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             PowerManager powerManager,
             BatteryController batteryController,
-            TunerService tunerService) {
+            TunerService tunerService,
+            FeatureFlags featureFlags) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -74,6 +77,7 @@
         mControlScreenOffAnimation = !getDisplayNeedsBlanking();
         mPowerManager = powerManager;
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
+        mFeatureFlags = featureFlags;
 
         tunerService.addTunable(
                 this,
@@ -200,8 +204,7 @@
      * then abruptly showing AOD.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return getAlwaysOn() && SystemProperties.getBoolean(
-                "persist.sysui.show_new_screen_on_transitions", false);
+        return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index e0df4f8..a6daed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -56,10 +56,12 @@
     private int mKeyguardStatusHeight;
 
     /**
-     * Height of {@link KeyguardUserSwitcherListView} when it
-     * is closed and only the current user's icon is visible.
+     * Height of user avatar used by the multi-user switcher. This could either be the
+     * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is
+     * visible, or it could be height of the avatar used by the
+     * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}.
      */
-    private int mKeyguardUserSwitcherHeight;
+    private int mUserSwitchHeight;
 
     /**
      * Preferred Y position of clock.
@@ -67,6 +69,11 @@
     private int mClockPreferredY;
 
     /**
+     * Preferred Y position of user avatar used by the multi-user switcher.
+     */
+    private int mUserSwitchPreferredY;
+
+    /**
      * Whether or not there is a custom clock face on keyguard.
      */
     private boolean mHasCustomClock;
@@ -181,20 +188,21 @@
      */
     public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
             float panelExpansion, int parentHeight, int keyguardStatusHeight,
-            int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
-            boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
-            int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
-            int cutoutTopInset) {
+            int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY,
+            boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
+            boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
+            float qsExpansion, int cutoutTopInset) {
         mMinTopMargin = statusBarMinHeight + (showLockIcon
                 ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
-                + keyguardUserSwitcherHeight;
+                + userSwitchHeight;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
-        mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
+        mUserSwitchHeight = userSwitchHeight;
         mClockPreferredY = clockPreferredY;
+        mUserSwitchPreferredY = userSwitchPreferredY;
         mHasCustomClock = hasCustomClock;
         mHasVisibleNotifs = hasVisibleNotifs;
         mDarkAmount = dark;
@@ -208,6 +216,7 @@
     public void run(Result result) {
         final int y = getClockY(mPanelExpansion, mDarkAmount);
         result.clockY = y;
+        result.userSwitchY = getUserSwitcherY(mPanelExpansion);
         result.clockYFullyDozing = getClockY(
                 1.0f /* panelExpansion */, 1.0f /* darkAmount */);
         result.clockAlpha = getClockAlpha(y);
@@ -241,7 +250,7 @@
 
     private int getExpandedPreferredClockY() {
         if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
-            return mMinTopMargin;
+            return mMinTopMargin + mUserSwitchHeight;
         }
         return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
                 : getExpandedClockPosition();
@@ -257,7 +266,7 @@
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
         float y = containerCenter
-                - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
+                - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT
                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
         if (y < mMinTopMargin) {
             y = mMinTopMargin;
@@ -299,6 +308,17 @@
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
     }
 
+    private int getUserSwitcherY(float panelExpansion) {
+        float userSwitchYRegular = mUserSwitchPreferredY;
+        float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight;
+
+        // Move user-switch up while collapsing the shade
+        float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
+        float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion);
+
+        return (int) (userSwitchY + mEmptyDragAmount);
+    }
+
     /**
      * We might want to fade out the clock when the user is swiping up.
      * One exception is when the bouncer will become visible, in this cause the clock
@@ -341,6 +361,11 @@
         public int clockY;
 
         /**
+         * The y translation of the multi-user switch.
+         */
+        public int userSwitchY;
+
+        /**
          * The y translation of the clock when we're fully dozing.
          */
         public int clockYFullyDozing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
new file mode 100644
index 0000000..377fb92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.keyguard.CarrierTextController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
+public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+    private final CarrierTextController mCarrierTextController;
+
+    @Inject
+    public KeyguardStatusBarViewController(
+            KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+        super(view);
+        mCarrierTextController = carrierTextController;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mCarrierTextController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index d9cb9ce..16f36b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -168,6 +168,6 @@
     }
 
     protected DetailAdapter getUserDetailAdapter() {
-        return mUserSwitcherController.userDetailAdapter;
+        return mUserSwitcherController.mUserDetailAdapter;
     }
 }
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 3b09eda..8aadef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,10 @@
 
 import static android.view.View.GONE;
 
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -66,6 +70,8 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintSet;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -77,6 +83,8 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
@@ -95,9 +103,11 @@
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -133,6 +143,7 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
@@ -292,14 +303,13 @@
             new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
                 @Override
                 public void onKeyguardUserSwitcherChanged(boolean open) {
-                    if (mKeyguardUserSwitcherController != null
-                            && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
-                        return;
+                    if (mKeyguardUserSwitcherController == null) {
+                        updateUserSwitcherVisibility(false);
+                    } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+                        updateUserSwitcherVisibility(open
+                                && mKeyguardStateController.isShowing()
+                                && !mKeyguardStateController.isKeyguardFadingAway());
                     }
-
-                    updateUserSwitcherVisibility(open
-                            && mKeyguardStateController.isShowing()
-                            && !mKeyguardStateController.isKeyguardFadingAway());
                 }
             };
 
@@ -315,7 +325,10 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
     private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+    private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+    private final QSDetailDisplayer mQSDetailDisplayer;
     private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
     private final ControlsComponent mControlsComponent;
@@ -327,8 +340,11 @@
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
+    private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+    private boolean mKeyguardUserSwitcherIsShowing;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
+    private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -352,6 +368,7 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardQsUserSwitchEnabled;
     private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
@@ -578,7 +595,10 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+            KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
+            QSDetailDisplayer qsDetailDisplayer,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
@@ -604,10 +624,16 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+        mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mFeatureFlags = featureFlags;
+        mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
         mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+        mQSDetailDisplayer = qsDetailDisplayer;
         mKeyguardUserSwitcherEnabled = mResources.getBoolean(
                 com.android.internal.R.bool.config_keyguardUserSwitcher);
+        mKeyguardQsUserSwitchEnabled =
+                mKeyguardUserSwitcherEnabled && mResources.getBoolean(
+                        R.bool.config_keyguard_user_switch_opens_qs_details);
         mView.setWillNotDraw(!DEBUG);
         mLayoutInflater = layoutInflater;
         mFalsingManager = falsingManager;
@@ -688,14 +714,22 @@
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
 
+        UserAvatarView userAvatarView = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
 
         if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
-            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
-            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+            if (mKeyguardQsUserSwitchEnabled) {
+                ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
+                userAvatarView = (UserAvatarView) stub.inflate();
+            } else {
+                ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+                keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
+            }
         }
 
         updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+                userAvatarView,
+                mKeyguardStatusBar,
                 keyguardUserSwitcherView);
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -741,6 +775,10 @@
         });
 
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
+        // dynamically apply the split shade value overrides.
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+            updateResources();
+        }
     }
 
     @Override
@@ -770,6 +808,8 @@
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+            UserAvatarView userAvatarView,
+            KeyguardStatusBarView keyguardStatusBarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
@@ -777,6 +817,12 @@
         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
         mKeyguardStatusViewController.init();
 
+        KeyguardStatusBarViewComponent statusBarViewComponent =
+                mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
+        mKeyguarStatusBarViewController =
+                statusBarViewComponent.getKeyguardStatusBarViewController();
+        mKeyguarStatusBarViewController.init();
+
         // Re-associate the clock container with the keyguard clock switch.
         KeyguardClockSwitchController keyguardClockSwitchController =
                 statusViewComponent.getKeyguardClockSwitchController();
@@ -789,18 +835,27 @@
             mKeyguardUserSwitcherController.removeCallback();
         }
 
+        mKeyguardQsUserSwitchController = null;
+        mKeyguardUserSwitcherController = null;
+
         // Re-associate the KeyguardUserSwitcherController
-        if (keyguardUserSwitcherView != null) {
+        if (userAvatarView != null) {
+            KeyguardQsUserSwitchComponent userSwitcherComponent =
+                    mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
+            mKeyguardQsUserSwitchController =
+                    userSwitcherComponent.getKeyguardQsUserSwitchController();
+            mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
+            mKeyguardQsUserSwitchController.init();
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+        } else if (keyguardUserSwitcherView != null) {
             KeyguardUserSwitcherComponent userSwitcherComponent =
                     mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
-
             mKeyguardUserSwitcherController =
                     userSwitcherComponent.getKeyguardUserSwitcherController();
             mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
             mKeyguardUserSwitcherController.init();
             mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
         } else {
-            mKeyguardUserSwitcherController = null;
             mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
         }
     }
@@ -840,11 +895,21 @@
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
 
+        // In order to change the constraints at runtime, all children of the Constraint Layout
+        // must have ids.
+        ensureAllViewsHaveIds(mNotificationContainerParent);
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
         if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
-            // In order to change the constraints at runtime, all children of the Constraint Layout
-            // must have ids.
-            ensureAllViewsHaveIds(mNotificationContainerParent);
+            constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
+            constraintSet.connect(
+                    R.id.notification_stack_scroller, START,
+                    R.id.qs_edge_guideline, START);
+        } else {
+            constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
+            constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
         }
+        constraintSet.applyTo(mNotificationContainerParent);
     }
 
     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
@@ -856,6 +921,25 @@
         }
     }
 
+    private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
+        View view = mView.findViewById(viewId);
+        if (view != null) {
+            int index = mView.indexOfChild(view);
+            mView.removeView(view);
+            if (enabled) {
+                view = mLayoutInflater.inflate(layoutId, mView, false);
+                mView.addView(view, index);
+            } else {
+                view = null;
+            }
+        } else if (enabled) {
+            // It's possible the stub was never inflated if the configuration changed
+            ViewStub stub = mView.findViewById(stubId);
+            view = stub.inflate();
+        }
+        return view;
+    }
+
     private void reInflateViews() {
         if (DEBUG) Log.d(TAG, "reInflateViews");
         // Re-inflate the status view group.
@@ -867,26 +951,27 @@
         mView.addView(keyguardStatusView, index);
 
         // Re-inflate the keyguard user switcher group.
-        boolean showUserSwitcher =
-                mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
-        KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
-                R.id.keyguard_user_switcher_view);
-        if (keyguardUserSwitcherView != null) {
-            index = mView.indexOfChild(keyguardUserSwitcherView);
-            mView.removeView(keyguardUserSwitcherView);
-            if (showUserSwitcher) {
-                keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
-                        R.layout.keyguard_user_switcher, mView, false);
-                mView.addView(keyguardUserSwitcherView, index);
-            }
-        } else if (showUserSwitcher) {
-            // It's possible the user switcher was never inflated if the configuration changed
-            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
-            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
-        }
+        boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled();
+        boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
+        boolean showKeyguardUserSwitcher =
+                !mKeyguardQsUserSwitchEnabled
+                        && mKeyguardUserSwitcherEnabled
+                        && isUserSwitcherEnabled;
+        UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+                R.id.keyguard_qs_user_switch_view /* viewId */,
+                R.id.keyguard_qs_user_switch_stub /* stubId */,
+                R.layout.keyguard_qs_user_switch /* layoutId */,
+                showQsUserSwitch /* enabled */);
+        KeyguardUserSwitcherView keyguardUserSwitcherView =
+                (KeyguardUserSwitcherView) reInflateStub(
+                        R.id.keyguard_user_switcher_view /* viewId */,
+                        R.id.keyguard_user_switcher_stub /* stubId */,
+                        R.layout.keyguard_user_switcher /* layoutId */,
+                        showKeyguardUserSwitcher /* enabled */);
 
         mBigClockContainer.removeAllViews();
-        updateViewControllers(keyguardStatusView, keyguardUserSwitcherView);
+        updateViewControllers(
+                keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
@@ -910,6 +995,13 @@
                 false,
                 false,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
                     mBarState,
@@ -1009,10 +1101,15 @@
             int totalHeight = mView.getHeight();
             int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
             int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight);
+            int userSwitcherPreferredY = mStatusBarMinHeight;
             boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
             final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
                     .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
             mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
+            int userIconHeight = mKeyguardQsUserSwitchController != null
+                    ? mKeyguardQsUserSwitchController.getUserIconHeight()
+                    : (mKeyguardUserSwitcherController != null
+                            ? mKeyguardUserSwitcherController.getUserIconHeight() : 0);
             mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
                     mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
                     getExpandedFraction(),
@@ -1021,9 +1118,8 @@
                             ? mKeyguardStatusViewController.getHeight()
                             : (int) (mKeyguardStatusViewController.getHeight()
                                     - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
-                    mKeyguardUserSwitcherController == null
-                            ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
-                    clockPreferredY, hasCustomClock(),
+                    userIconHeight,
+                    clockPreferredY, userSwitcherPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
                     mUpdateMonitor.canShowLockIcon(),
@@ -1033,11 +1129,16 @@
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
+            if (mKeyguardQsUserSwitchController != null) {
+                mKeyguardQsUserSwitchController.updatePosition(
+                        mClockPositionResult.clockX,
+                        mClockPositionResult.userSwitchY,
+                        animateClock);
+            }
             if (mKeyguardUserSwitcherController != null) {
                 mKeyguardUserSwitcherController.updatePosition(
                         mClockPositionResult.clockX,
-                        mClockPositionResult.clockY
-                                - mKeyguardUserSwitcherController.getUserIconHeight(),
+                        mClockPositionResult.userSwitchY,
                         animateClock);
             }
             updateNotificationTranslucency();
@@ -1180,6 +1281,9 @@
 
     private void updateClock() {
         mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
         }
@@ -1271,6 +1375,12 @@
         }
     }
 
+    public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
+        traceQsJank(true /* startTracing */, false /* wasCancelled */);
+        flingSettings(0 /* velocity */, FLING_EXPAND);
+        mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+    }
+
     public void expandWithoutQs() {
         if (isQsExpanded()) {
             flingSettings(0 /* velocity */, FLING_COLLAPSE);
@@ -1933,6 +2043,10 @@
     }
 
     private float calculateQsTopPadding() {
+        // in split shade mode we want notifications to be directly below status bar
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) {
+            return 0f;
+        }
         if (mKeyguardShowing && (mQsExpandImmediate
                 || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
 
@@ -2572,7 +2686,9 @@
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+            if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+                mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+            }
         }
         if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             mAffordanceHelper.animateHideLeftRightIcon();
@@ -2814,12 +2930,12 @@
     }
 
     /**
-     * Updates the vertical position of the panel so it is positioned closer to the touch
+     * Updates the horizontal position of the panel so it is positioned closer to the touch
      * responsible for opening the panel.
      *
      * @param x the x-coordinate the touch event
      */
-    protected void updateVerticalPanelPosition(float x) {
+    protected void updateHorizontalPanelPosition(float x) {
         if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
             resetHorizontalPanelPosition();
             return;
@@ -3166,6 +3282,13 @@
                 true /* keyguardFadingAway */,
                 false /* goingToFullShade */,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
                     mBarState,
@@ -3396,7 +3519,7 @@
                 }
                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                     mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
-                    updateVerticalPanelPosition(event.getX());
+                    updateHorizontalPanelPosition(event.getX());
                     handled = true;
                 }
                 handled |= super.onTouch(v, event);
@@ -3432,6 +3555,12 @@
     }
 
     private void updateUserSwitcherVisibility(boolean open) {
+        // Do not update if previously called with the same state.
+        if (mKeyguardUserSwitcherIsShowing == open) {
+            return;
+        }
+        mKeyguardUserSwitcherIsShowing = open;
+
         if (open) {
             animateKeyguardStatusBarOut();
             mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
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 e63902f..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -35,7 +35,6 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -180,6 +179,7 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -440,6 +440,7 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
+    private final FeatureFlags mFeatureFlags;
 
     private final List<ExpansionChangedListener> mExpansionChangedListeners;
 
@@ -762,7 +763,8 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory) {
+            BrightnessSlider.Factory brightnessSliderFactory,
+            FeatureFlags featureFlags) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -840,6 +842,7 @@
         mDemoModeController = demoModeController;
         mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
+        mFeatureFlags = featureFlags;
 
         mExpansionChangedListeners = new ArrayList<>();
 
@@ -1181,9 +1184,11 @@
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
 
-        if (getEnableLightReveal()) {
+        if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) {
             mLightRevealScrim.setVisibility(View.VISIBLE);
             mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+        } else {
+            mLightRevealScrim.setVisibility(View.GONE);
         }
 
         mNotificationPanelViewController.initDependencies(
@@ -3614,7 +3619,7 @@
 
     @Override
     public void onDozeAmountChanged(float linear, float eased) {
-        if (getEnableLightReveal()) {
+        if (mFeatureFlags.useNewLockscreenAnimations()) {
             mLightRevealScrim.setRevealAmount(1f - linear);
         }
     }
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 9e9533d..b572c57 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
@@ -47,6 +47,7 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -200,7 +201,8 @@
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory) {
+            BrightnessSlider.Factory brightnessSliderFactory,
+            FeatureFlags featureFlags) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -279,6 +281,7 @@
                 notificationShadeDepthController,
                 statusBarTouchableRegionManager,
                 notificationIconAreaController,
-                brightnessSliderFactory);
+                brightnessSliderFactory,
+                featureFlags);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
new file mode 100644
index 0000000..38f3bc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switch on the Keyguard that is used for opening the QS user panel.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+
+    private static final String TAG = "KeyguardQsUserSwitchController";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final AnimationProperties ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+    private final Context mContext;
+    private Resources mResources;
+    private final UserSwitcherController mUserSwitcherController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private UserSwitcherController.BaseUserAdapter mAdapter;
+    private final KeyguardStateController mKeyguardStateController;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+    private final KeyguardUserDetailAdapter mUserDetailAdapter;
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private UserManager mUserManager;
+    UserSwitcherController.UserRecord mCurrentUser;
+
+    // State info for the user switch and keyguard
+    private int mBarState;
+    private float mDarkAmount;
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+                    int oldState = mBarState;
+                    mBarState = newState;
+
+                    setKeyguardQsUserSwitchVisibility(
+                            newState,
+                            keyguardFadingAway,
+                            goingToFullShade,
+                            oldState);
+                }
+
+                @Override
+                public void onDozeAmountChanged(float linearAmount, float amount) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+                                linearAmount, amount));
+                    }
+                    setDarkAmount(amount);
+                }
+            };
+
+    @Inject
+    public KeyguardQsUserSwitchController(
+            UserAvatarView view,
+            Context context,
+            @Main Resources resources,
+            UserManager userManager,
+            ScreenLifecycle screenLifecycle,
+            UserSwitcherController userSwitcherController,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            DozeParameters dozeParameters,
+            UiEventLogger uiEventLogger) {
+        super(view);
+        if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
+        mContext = context;
+        mResources = resources;
+        mUserManager = userManager;
+        mScreenLifecycle = screenLifecycle;
+        mUserSwitcherController = userSwitcherController;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+                keyguardStateController, dozeParameters);
+        mUserDetailAdapter = new KeyguardUserDetailAdapter(mUserSwitcherController, mContext,
+                uiEventLogger);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        if (DEBUG) Log.d(TAG, "onInit");
+        mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                return null;
+            }
+        };
+
+        mView.setOnClickListener(v -> {
+            if (isListAnimating()) {
+                return;
+            }
+
+            // Tapping anywhere in the view will open QS user panel
+            openQsUserPanel();
+        });
+
+        updateView(true /* forceUpdate */);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        if (DEBUG) Log.d(TAG, "onViewAttached");
+        mAdapter.registerDataSetObserver(mDataSetObserver);
+        mDataSetObserver.onChanged();
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        if (DEBUG) Log.d(TAG, "onViewDetached");
+
+        mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+    }
+
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            updateView(false /* forceUpdate */);
+        }
+    };
+
+    /**
+     * @return true if the current user has changed
+     */
+    private boolean updateCurrentUser() {
+        UserSwitcherController.UserRecord previousUser = mCurrentUser;
+        mCurrentUser = null;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            UserSwitcherController.UserRecord r = mAdapter.getItem(i);
+            if (r.isCurrent) {
+                mCurrentUser = r;
+                return !mCurrentUser.equals(previousUser);
+            }
+        }
+        return mCurrentUser == null && previousUser != null;
+    }
+
+    /**
+     * @param forceUpdate whether to update view even if current user did not change
+     */
+    private void updateView(boolean forceUpdate) {
+        if (!updateCurrentUser() && !forceUpdate) {
+            return;
+        }
+
+        if (mCurrentUser == null) {
+            mView.setVisibility(View.GONE);
+            return;
+        }
+
+        mView.setVisibility(View.VISIBLE);
+
+        String currentUserName = mCurrentUser.info.name;
+        String contentDescription = null;
+
+        if (!TextUtils.isEmpty(currentUserName)) {
+            contentDescription = mContext.getString(
+                    R.string.accessibility_quick_settings_user,
+                    currentUserName);
+        }
+
+        if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
+            mView.setContentDescription(contentDescription);
+        }
+
+        if (mCurrentUser.picture == null) {
+            mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(),
+                    mCurrentUser.resolveId());
+        } else {
+            int avatarSize =
+                    (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+            Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
+            drawable.setColorFilter(
+                    mCurrentUser.isSwitchToEnabled ? null
+                            : mAdapter.getDisabledUserAvatarColorFilter());
+            mView.setDrawableWithBadge(drawable, mCurrentUser.info.id);
+        }
+    }
+
+    Drawable getDrawable(UserSwitcherController.UserRecord item) {
+        Drawable drawable;
+        if (item.isCurrent && item.isGuest) {
+            drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+        } else {
+            drawable = mAdapter.getIconDrawable(mContext, item);
+        }
+
+        int iconColorRes;
+        if (item.isSwitchToEnabled) {
+            iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+        } else {
+            iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+        }
+        drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+        Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+        drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+        return drawable;
+    }
+
+    /**
+     * Get the height of the keyguard user switcher view when closed.
+     */
+    public int getUserIconHeight() {
+        return mView.getHeight();
+    }
+
+    /**
+     * Set the visibility of the user avatar view based on some new state.
+     */
+    public void setKeyguardQsUserSwitchVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+    }
+
+    /**
+     * Update position of the view with an optional animation
+     */
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
+        PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+                ANIMATION_PROPERTIES, animate);
+    }
+
+    /**
+     * Set keyguard user avatar view alpha.
+     */
+    public void setAlpha(float alpha) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+            mView.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    private void setDarkAmount(float darkAmount) {
+        boolean isAwake = darkAmount != 0;
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+    }
+
+    private boolean isListAnimating() {
+        return mKeyguardVisibilityHelper.isVisibilityAnimating();
+    }
+
+    private void openQsUserPanel() {
+        mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
+    }
+
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
+    }
+
+    class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
+        KeyguardUserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+                UiEventLogger uiEventLogger) {
+            super(userSwitcherController, context, uiEventLogger);
+        }
+
+        @Override
+        public boolean shouldAnimate() {
+            return false;
+        }
+
+        @Override
+        public boolean onDoneButtonClicked() {
+            if (mNotificationPanelViewController != null) {
+                mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 1838391..d4029e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -121,6 +121,7 @@
     private Intent mSecondaryUserServiceIntent;
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
     private final UiEventLogger mUiEventLogger;
+    public final DetailAdapter mUserDetailAdapter;
 
     @Inject
     public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@@ -131,6 +132,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mActivityTaskManager = activityTaskManager;
         mUiEventLogger = uiEventLogger;
+        mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
         }
@@ -680,11 +682,7 @@
             if (item.isAddUser) {
                 iconRes = R.drawable.ic_add_circle;
             } else if (item.isGuest) {
-                if (item.isCurrent) {
-                    iconRes = R.drawable.ic_exit_to_app;
-                } else {
-                    iconRes = R.drawable.ic_avatar_guest_user;
-                }
+                iconRes = R.drawable.ic_avatar_guest_user;
             } else {
                 iconRes = R.drawable.ic_avatar_user;
             }
@@ -785,9 +783,20 @@
         }
     }
 
-    public final DetailAdapter userDetailAdapter = new DetailAdapter() {
+    public static class UserDetailAdapter implements DetailAdapter {
         private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
 
+        private final UserSwitcherController mUserSwitcherController;
+        private final Context mContext;
+        private final UiEventLogger mUiEventLogger;
+
+        UserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+                UiEventLogger uiEventLogger) {
+            mUserSwitcherController = userSwitcherController;
+            mContext = context;
+            mUiEventLogger = uiEventLogger;
+        }
+
         @Override
         public CharSequence getTitle() {
             return mContext.getString(R.string.quick_settings_user_title);
@@ -798,7 +807,7 @@
             UserDetailView v;
             if (!(convertView instanceof UserDetailView)) {
                 v = UserDetailView.inflate(context, parent, false);
-                v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger);
+                v.createAndSetAdapter(mUserSwitcherController, mUiEventLogger);
             } else {
                 v = (UserDetailView) convertView;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 47207d8..16998d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
+import java.io.PrintWriter;
 import java.util.Objects;
 
 public class WifiSignalController extends
@@ -232,6 +233,12 @@
         notifyListenersIfNecessary();
     }
 
+    @Override
+    public void dump(PrintWriter pw) {
+        super.dump(pw);
+        mWifiTracker.dump(pw);
+    }
+
     /**
      * Handler to receive the data activity on wifi.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 3f01414..9e78a66 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -194,7 +194,11 @@
         }
 
         mExecutor.execute(() -> {
-            mOverlayManager.commit(transaction.build());
+            try {
+                mOverlayManager.commit(transaction.build());
+            } catch (SecurityException | IllegalStateException e) {
+                Log.e(TAG, "setEnabled failed", e);
+            }
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 09335af..b67574d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -109,7 +109,7 @@
         dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
                 (OnClickListener) null);
         dialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                context.getString(R.string.qs_customize_remove), new OnClickListener() {
+                context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         // Tell the tuner (in main SysUI process) to clear all its settings.
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec61db5..8505703 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -366,7 +366,7 @@
             switch (args[i]) {
                 case "enable-text": {
                     String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
-                    int result = protoLogImpl.startTextLogging(mContext, groups, pw);
+                    int result = protoLogImpl.startTextLogging(groups, pw);
                     if (result == 0) {
                         pw.println("Starting logging on groups: " + Arrays.toString(groups));
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 449db61..fba0b00 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -20,7 +20,6 @@
 
 import android.animation.AnimationHandler;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -73,7 +72,6 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
-import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -211,8 +209,8 @@
     @Provides
     static SizeCompatUIController provideSizeCompatUIController(Context context,
             DisplayController displayController, DisplayImeController imeController,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+            SyncTransactionQueue syncQueue) {
+        return new SizeCompatUIController(context, displayController, imeController, syncQueue);
     }
 
     @WMSingleton
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index aa4122f..d3f9d641 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -54,7 +54,6 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -74,7 +73,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class CarrierTextControllerTest extends SysuiTestCase {
+public class CarrierTextManagerTest extends SysuiTestCase {
 
     private static final CharSequence SEPARATOR = " \u2014 ";
     private static final CharSequence INVALID_CARD_TEXT = "Invalid card";
@@ -95,7 +94,9 @@
     @Mock
     private WifiManager mWifiManager;
     @Mock
-    private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
@@ -104,13 +105,14 @@
     private TelephonyManager mTelephonyManager;
     @Mock
     private SubscriptionManager mSubscriptionManager;
-    private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
+    private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
 
-    private CarrierTextController mCarrierTextController;
+    private CarrierTextManager mCarrierTextManager;
     private TestableLooper mTestableLooper;
+    private Handler mMainHandler;
 
     private Void checkMainThread(InvocationOnMock inv) {
-        Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper();
+        Looper mainLooper = mMainHandler.getLooper();
         if (!mainLooper.isCurrentThread()) {
             fail("This call should be done from the main thread");
         }
@@ -122,35 +124,33 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
 
-        mContext.addMockSystemService(WifiManager.class, mWifiManager);
-        mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
+        mMainHandler = new Handler(mTestableLooper.getLooper());
         when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
-        mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
-        mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
         mContext.getOrCreateTestableResources().addOverride(
                 R.string.keyguard_sim_error_message_short, INVALID_CARD_TEXT);
         mContext.getOrCreateTestableResources().addOverride(
                 R.string.airplane_mode, AIRPLANE_MODE_TEXT);
-        mDependency.injectMockDependency(WakefulnessLifecycle.class);
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                new Handler(mTestableLooper.getLooper()));
-        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
-        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
 
         doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
                 .registerCallback(any(KeyguardUpdateMonitorCallback.class));
         doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
                 .removeCallback(any(KeyguardUpdateMonitorCallback.class));
 
-        mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
+        mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
         when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
         when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
 
-        mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
+        mCarrierTextManager = new CarrierTextManager.Builder(
+                mContext, mContext.getResources(), mWifiManager, mConnectivityManager,
+                mTelephonyManager, mWakefulnessLifecycle, new Handler(mTestableLooper.getLooper()),
+                mMainHandler, mKeyguardUpdateMonitor)
+                .setShowAirplaneMode(true)
+                .setShowMissingSim(true)
+                .build();
         // This should not start listening on any of the real dependencies but will test that
         // callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
-        mCarrierTextController.setListening(mCarrierTextCallback);
+        mCarrierTextManager.setListening(mCarrierTextCallback);
         mTestableLooper.processAllMessages();
     }
 
@@ -165,8 +165,8 @@
         TestableLooper testableLooper = new TestableLooper(thread.getLooper());
         Handler h = new Handler(testableLooper.getLooper());
         h.post(() -> {
-            mCarrierTextController.setListening(null);
-            mCarrierTextController.setListening(mCarrierTextCallback);
+            mCarrierTextManager.setListening(null);
+            mCarrierTextManager.setListening(mCarrierTextCallback);
         });
         testableLooper.processAllMessages();
         mTestableLooper.processAllMessages();
@@ -183,11 +183,11 @@
         when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -205,12 +205,12 @@
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        mCarrierTextController.mCallback.onSimStateChanged(3, 1,
+        mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -223,7 +223,7 @@
         reset(mCarrierTextCallback);
         when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
         // Update carrier text. It should ignore error state of subId 3 in inactive slotId.
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals("TEST_CARRIER", captor.getValue().carrierText);
@@ -237,9 +237,9 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
-        mCarrierTextController.mCallback.onSimStateChanged(0, -3,
+        mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
-        mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
         verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
     }
 
@@ -257,23 +257,23 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
-        mCarrierTextController.mCallback.onSimStateChanged(0, 1,
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(
-                any(CarrierTextController.CarrierTextCallbackInfo.class));
+                any(CarrierTextManager.CarrierTextCallbackInfo.class));
     }
 
     @Test
     public void testCallback() {
         reset(mCarrierTextCallback);
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
         mTestableLooper.processAllMessages();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals(mCarrierTextCallbackInfo, captor.getValue());
     }
@@ -282,8 +282,8 @@
     public void testNullingCallback() {
         reset(mCarrierTextCallback);
 
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
-        mCarrierTextController.setListening(null);
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+        mCarrierTextManager.setListening(null);
 
         // This shouldn't produce NPE
         mTestableLooper.processAllMessages();
@@ -301,15 +301,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(1, info.listOfCarriers.length);
         assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
         assertEquals(1, info.subscriptionIds.length);
@@ -326,15 +326,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(1, info.listOfCarriers.length);
         assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
         assertEquals(1, info.subscriptionIds.length);
@@ -346,16 +346,16 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_NULL);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-            TelephonyManager.SIM_STATE_READY);
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -380,11 +380,11 @@
         when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -407,15 +407,15 @@
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(0, info.listOfCarriers.length);
         assertEquals(0, info.subscriptionIds.length);
 
@@ -428,16 +428,16 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-            TelephonyManager.SIM_STATE_READY);
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -458,11 +458,11 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -483,11 +483,11 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -509,11 +509,11 @@
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index c2ade81..d67fe6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -69,6 +69,8 @@
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     @Mock
     private LatencyTracker mLatencyTracker;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
 
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
 
@@ -84,7 +86,8 @@
                 .thenReturn(mKeyguardMessageArea);
         mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mKeyguardMessageAreaControllerFactory, mLatencyTracker) {
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker,
+                mEmergencyButtonController) {
             @Override
             void resetState() {
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 826be2b..4beec57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -54,11 +54,11 @@
 public class KeyguardDisplayManagerTest extends SysuiTestCase {
 
     @Mock
+    private NavigationBarController mNavigationBarController;
+    @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-
     @Mock
     private DisplayManager mDisplayManager;
-
     @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
 
@@ -76,9 +76,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
-        mDependency.injectMockDependency(NavigationBarController.class);
-        mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory,
-                mBackgroundExecutor));
+        mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
+                mKeyguardStatusViewComponentFactory, mBackgroundExecutor));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index c69ec1a..6d0c640 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -49,6 +49,8 @@
     @Mock
     private lateinit var mLatencyTracker: LatencyTracker
     @Mock
+    private lateinit var mEmergencyButtonController: EmergencyButtonController
+    @Mock
     private lateinit
     var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
     @Mock
@@ -72,7 +74,8 @@
                 .thenReturn(mKeyguardMessageAreaController)
         mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
         mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mLatencyTracker, mKeyguardMessageAreaControllerFactory)
+                mLatencyTracker, mEmergencyButtonController,
+                mKeyguardMessageAreaControllerFactory)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 31cc7bb..8d1e1a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -67,6 +67,8 @@
     private LatencyTracker mLatencyTracker;
     @Mock
     private LiftToActivateListener mLiftToactivateListener;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
     private View mDeleteButton;
@@ -92,7 +94,7 @@
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
-                mFalsingCollector) {
+                mEmergencyButtonController, mFalsingCollector) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 3b7f4b8..9296d3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -59,6 +59,10 @@
     @Mock
     private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
     @Mock
+    private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
+    @Mock
     private KeyguardInputViewController mKeyguardInputViewController;
     @Mock
     private KeyguardInputView mInputView;
@@ -76,9 +80,12 @@
                 any(KeyguardSecurityCallback.class)))
                 .thenReturn(mKeyguardInputViewController);
         when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
+                .thenReturn(mEmergencyButtonController);
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
-                mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+                mLayoutInflater, mKeyguardSecurityViewControllerFactory,
+                mEmergencyButtonControllerFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 069699c..6d8c372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -173,4 +173,30 @@
         mDozeUi.transitionTo(UNINITIALIZED, DOZE);
         verify(mHost).setAnimateWakeup(eq(false));
     }
+
+    @Test
+    public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+        // Tell doze that keyguard is not visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+        // Since we're controlling the unlocked screen off animation, verify that we've asked to
+        // control the screen off animation despite being unlocked.
+        verify(mDozeParameters).setControlScreenOffAnimation(true);
+    }
+
+    @Test
+    public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+
+        // Tell doze that keyguard is not visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+        // Since we're not controlling the unlocked screen off animation, verify that we haven't
+        // asked to control the screen off animation since we're unlocked.
+        verify(mDozeParameters).setControlScreenOffAnimation(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 00943bc..b8c37fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,13 +29,17 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
 
+import com.android.systemui.R;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -45,6 +51,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -117,4 +124,20 @@
         mViewMediator.mViewMediatorCallback.keyguardGone();
         verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false));
     }
+
+    @Test
+    public void testIsAnimatingScreenOff() {
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+        mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
+        mViewMediator.setDozing(true);
+
+        // Mid-doze, we should be animating the screen off animation.
+        mViewMediator.onDozeAmountChanged(0.5f, 0.5f);
+        assertTrue(mViewMediator.isAnimatingScreenOff());
+
+        // Once we're 100% dozed, the screen off animation should be completed.
+        mViewMediator.onDozeAmountChanged(1f, 1f);
+        assertFalse(mViewMediator.isAnimatingScreenOff());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 2db224f..e88c728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -315,10 +315,9 @@
         }
         mediaDataManager.onNotificationAdded(KEY, notif)
 
-        // THEN it loads and uses the default background color
+        // THEN it still loads
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
-        assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 1ec1da4..e855834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -33,7 +33,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -54,7 +54,7 @@
 
     private QSCarrierGroupController mQSCarrierGroupController;
     private NetworkController.SignalCallback mSignalCallback;
-    private CarrierTextController.CarrierTextCallback mCallback;
+    private CarrierTextManager.CarrierTextCallback mCallback;
     @Mock
     private QSCarrierGroup mQSCarrierGroup;
     @Mock
@@ -62,9 +62,9 @@
     @Mock
     private NetworkController mNetworkController;
     @Mock
-    private CarrierTextController.Builder mCarrierTextControllerBuilder;
+    private CarrierTextManager.Builder mCarrierTextControllerBuilder;
     @Mock
-    private CarrierTextController mCarrierTextController;
+    private CarrierTextManager mCarrierTextManager;
     private TestableLooper mTestableLooper;
 
     @Before
@@ -83,11 +83,11 @@
                 .thenReturn(mCarrierTextControllerBuilder);
         when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
                 .thenReturn(mCarrierTextControllerBuilder);
-        when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController);
+        when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
 
         doAnswer(invocation -> mCallback = invocation.getArgument(0))
-                .when(mCarrierTextController)
-                .setListening(any(CarrierTextController.CarrierTextCallback.class));
+                .when(mCarrierTextManager)
+                .setListening(any(CarrierTextManager.CarrierTextCallback.class));
 
         when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
         when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class));
@@ -113,8 +113,8 @@
                 (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c1 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c1 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 false,
@@ -122,8 +122,8 @@
         mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c2 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c2 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
@@ -131,8 +131,8 @@
         mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c3 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c3 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 false,
@@ -140,8 +140,8 @@
         mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -159,8 +159,8 @@
                 (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c1 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c1 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 false,
@@ -168,8 +168,8 @@
         mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c2 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c2 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -177,8 +177,8 @@
         mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c3 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c3 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 false,
@@ -186,8 +186,8 @@
         mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
@@ -203,8 +203,8 @@
         when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn(
                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
 
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -223,8 +223,8 @@
 
     @Test
     public void testNoEmptyVisibleView_airplaneMode() {
-        CarrierTextController.CarrierTextCallbackInfo
-                info = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                info = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index fa253e6..421c6f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.reset;
@@ -35,6 +37,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -57,6 +60,7 @@
     @Mock private PowerManager mPowerManager;
     @Mock private TunerService mTunerService;
     @Mock private BatteryController mBatteryController;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setup() {
@@ -67,7 +71,8 @@
             mAlwaysOnDisplayPolicy,
             mPowerManager,
             mBatteryController,
-            mTunerService
+            mTunerService,
+            mFeatureFlags
         );
     }
     @Test
@@ -111,4 +116,37 @@
 
         assertThat(mDozeParameters.getAlwaysOn()).isFalse();
     }
+
+    @Test
+    public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+        when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true);
+
+        assertTrue(mDozeParameters.shouldControlUnlockedScreenOff());
+
+        // Trigger the setter for the current value.
+        mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+        // We should have asked power manager not to doze after screen off no matter what, since
+        // we're animating and controlling screen off.
+        verify(mPowerManager).setDozeAfterScreenOff(eq(false));
+    }
+
+    @Test
+    public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+        when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(false);
+
+        assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
+
+        // Trigger the setter for the current value.
+        mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+        // We should have asked power manager to doze only if we're not controlling screen off
+        // normally.
+        verify(mPowerManager).setDozeAfterScreenOff(
+                eq(!mDozeParameters.shouldControlScreenOff()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 4162884..9b623f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -394,8 +394,8 @@
     private void positionClock() {
         mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
                 mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
-                0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
-                mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+                0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */,
+                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
                 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
                 mCutoutTopInset);
         mClockPositionAlgorithm.run(mClockPosition);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index d0e7031..d69d2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -48,6 +48,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -58,6 +60,8 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.R;
@@ -70,6 +74,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -194,14 +199,24 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
-    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+    private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
+    @Mock
+    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+    @Mock
+    private QSDetailDisplayer mQSDetailDisplayer;
     @Mock
     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock
+    private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+    @Mock
+    private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
+    @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock
     private KeyguardStatusViewController mKeyguardStatusViewController;
     @Mock
+    private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+    @Mock
     private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     @Mock
     private AuthController mAuthController;
@@ -216,14 +231,13 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    @Mock
     private AmbientState mAmbientState;
     @Mock
     private UserManager mUserManager;
 
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
+    private NotificationsQuickSettingsContainer mNotificationContainerParent;
 
     @Before
     public void setup() {
@@ -256,6 +270,7 @@
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
         when(mView.findViewById(R.id.notification_container_parent))
                 .thenReturn(mNotificationContainerParent);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
@@ -289,6 +304,10 @@
                 new ViewGroup.LayoutParams(600, 400));
         when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn(
                 new ViewGroup.LayoutParams(600, 400));
+        when(mKeyguardStatusBarViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusBarViewComponent);
+        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+                .thenReturn(mKeyguardStatusBarViewController);
 
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
@@ -305,7 +324,10 @@
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
-                mKeyguardUserSwitcherComponent,
+                mKeyguardQsUserSwitchComponentFactory,
+                mKeyguardUserSwitcherComponentFactory,
+                mKeyguardStatusBarViewComponentFactory,
+                mQSDetailDisplayer,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
@@ -432,16 +454,11 @@
 
     @Test
     public void testAllChildrenOfNotificationContainer_haveIds() {
-        when(mNotificationContainerParent.getChildCount()).thenReturn(2);
         when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
         when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
 
-        View view1 = new View(mContext);
-        view1.setId(1);
-        when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1);
-
-        View view2 = mock(View.class);
-        when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2);
+        mNotificationContainerParent.addView(newViewWithId(1));
+        mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
 
         mNotificationPanelViewController.updateResources();
 
@@ -449,6 +466,51 @@
         assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
     }
 
+    @Test
+    public void testSinglePaneShadeLayout_isAlignedToParent() {
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+        mNotificationPanelViewController.updateResources();
+
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+                R.id.notification_stack_scroller).layout;
+        assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID);
+        assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID);
+    }
+
+    @Test
+    public void testSplitShadeLayout_isAlignedToGuideline() {
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+        mNotificationPanelViewController.updateResources();
+
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+                R.id.notification_stack_scroller).layout;
+        assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline);
+        assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline);
+    }
+
+    private View newViewWithId(int id) {
+        View view = new View(mContext);
+        view.setId(id);
+        ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.setLayoutParams(layoutParams);
+        return view;
+    }
+
     private void onTouchEvent(MotionEvent ev) {
         mTouchHandler.onTouch(mView, ev);
     }
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 cae488a..253460d 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
@@ -97,6 +97,7 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -258,6 +259,7 @@
     @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
+    @Mock private FeatureFlags mFeatureFlags;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -418,7 +420,8 @@
                 mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager,
                 mNotificationIconAreaController,
-                mBrightnessSliderFactory);
+                mBrightnessSliderFactory,
+                mFeatureFlags);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index b36626f..0d323fb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -23,6 +23,9 @@
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
 import java.io.PrintWriter;
 
 /**
@@ -31,11 +34,13 @@
 final class AccessibilityShellCommand extends ShellCommand {
     final @NonNull AccessibilityManagerService mService;
     final @NonNull SystemActionPerformer mSystemActionPerformer;
+    final @NonNull WindowManagerInternal mWindowManagerService;
 
     AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
             @NonNull SystemActionPerformer systemActionPerformer) {
         mService = service;
         mSystemActionPerformer = systemActionPerformer;
+        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
     }
 
     @Override
@@ -53,6 +58,10 @@
             case "call-system-action": {
                 return runCallSystemAction();
             }
+            case "start-trace":
+                return startTrace();
+            case "stop-trace":
+                return stopTrace();
         }
         return -1;
     }
@@ -98,6 +107,20 @@
         return -1;
     }
 
+    private int startTrace() {
+        WindowManagerInternal.AccessibilityControllerInternal ac =
+                mWindowManagerService.getAccessibilityController();
+        ac.startTrace();
+        return 0;
+    }
+
+    private int stopTrace() {
+        WindowManagerInternal.AccessibilityControllerInternal ac =
+                mWindowManagerService.getAccessibilityController();
+        ac.stopTrace();
+        return 0;
+    }
+
     private Integer parseUserId() {
         final String option = getNextOption();
         if (option != null) {
@@ -123,5 +146,9 @@
         pw.println("    Get whether binding to services provided by instant apps is allowed.");
         pw.println("  call-system-action <ACTION_ID>");
         pw.println("    Calls the system action with the given action id.");
+        pw.println("  start-trace");
+        pw.println("    Start the debug tracing.");
+        pw.println("  stop-trace");
+        pw.println("    Stop the debug tracing.");
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 25890b0..542a260 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,8 +81,6 @@
         ":dumpstate_aidl",
         ":framework_native_aidl",
         ":gsiservice_aidl",
-        ":idmap2_aidl",
-        ":idmap2_core_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
         ":storaged_aidl",
@@ -230,8 +228,5 @@
         "java/com/android/server/connectivity/QosCallbackAgentConnection.java",
         "java/com/android/server/connectivity/QosCallbackTracker.java",
         "java/com/android/server/connectivity/TcpKeepaliveController.java",
-        "java/com/android/server/connectivity/Vpn.java",
-        "java/com/android/server/connectivity/VpnIkev2Utils.java",
-        "java/com/android/server/net/LockdownVpnTracker.java",
     ],
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 277152d..558fbc2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
@@ -45,8 +44,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -95,6 +95,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
@@ -138,7 +139,6 @@
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
-import android.net.VpnService;
 import android.net.VpnTransportInfo;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
@@ -170,8 +170,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.Credentials;
-import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -187,9 +185,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
@@ -214,9 +209,7 @@
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
-import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.utils.PriorityDump;
 
@@ -309,18 +302,7 @@
 
     private final PerUidCounter mNetworkRequestCounter;
 
-    private KeyStore mKeyStore;
-
-    @VisibleForTesting
-    @GuardedBy("mVpns")
-    protected final SparseArray<Vpn> mVpns = new SparseArray<>();
-
-    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
-    // a direct call to LockdownVpnTracker.isEnabled().
-    @GuardedBy("mVpns")
-    private boolean mLockdownEnabled;
-    @GuardedBy("mVpns")
-    private LockdownVpnTracker mLockdownTracker;
+    private volatile boolean mLockdownEnabled;
 
     /**
      * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
@@ -571,6 +553,12 @@
     private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
 
     /**
+     * used internally when setting the default networks for OemNetworkPreferences.
+     * obj = OemNetworkPreferences
+     */
+    private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -755,6 +743,27 @@
             }
         }
 
+        // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying
+        // network type, to preserve previous behaviour.
+        private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) {
+            if (vpnNai != mService.getLegacyLockdownNai()) return;
+
+            if (vpnNai.declaredUnderlyingNetworks == null
+                    || vpnNai.declaredUnderlyingNetworks.length != 1) {
+                Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: "
+                        + Arrays.toString(vpnNai.declaredUnderlyingNetworks));
+                return;
+            }
+            final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
+                    vpnNai.declaredUnderlyingNetworks[0]);
+            if (underlyingNai == null) return;
+
+            final int type = underlyingNai.networkInfo.getType();
+            final DetailedState state = DetailedState.CONNECTED;
+            maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */);
+            mService.sendLegacyNetworkBroadcast(underlyingNai, state, type);
+        }
+
         /** Adds the given network to the specified legacy type list. */
         public void add(int type, NetworkAgentInfo nai) {
             if (!isTypeSupported(type)) {
@@ -772,9 +781,17 @@
 
             // Send a broadcast if this is the first network of its type or if it's the default.
             final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
+
+            // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts
+            // to preserve previous behaviour.
+            final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED);
             if ((list.size() == 1) || isDefaultNetwork) {
-                maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
-                mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+                maybeLogBroadcast(nai, state, type, isDefaultNetwork);
+                mService.sendLegacyNetworkBroadcast(nai, state, type);
+            }
+
+            if (type == TYPE_VPN && state == DetailedState.CONNECTED) {
+                maybeSendLegacyLockdownBroadcast(nai);
             }
         }
 
@@ -969,13 +986,6 @@
         }
 
         /**
-         * Get a reference to the system keystore.
-         */
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
-        }
-
-        /**
          * @see ProxyTracker
          */
         public ProxyTracker makeProxyTracker(@NonNull Context context,
@@ -1039,10 +1049,10 @@
 
         mMetricsLog = logger;
         mNetworkRanker = new NetworkRanker();
-        final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
-                -1, NetworkRequest.Type.REQUEST);
-        mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder(),
-                null /* attributionTag */);
+        final NetworkRequest defaultInternetRequest = createDefaultRequest();
+        mDefaultRequest = new NetworkRequestInfo(
+                defaultInternetRequest, null, new Binder(),
+                null /* attributionTags */);
         mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
         mDefaultNetworkRequests.add(mDefaultRequest);
         mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1084,7 +1094,6 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
-        mKeyStore = mDeps.getKeyStore();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = new LocationPermissionChecker(mContext);
@@ -1173,43 +1182,15 @@
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
-        // Set up the listener for user state for creating user VPNs.
+        // Listen for user add/removes to inform PermissionMonitor.
         // Should run on mHandler to avoid any races.
         IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_STARTED);
-        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
         intentFilter.addAction(Intent.ACTION_USER_ADDED);
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
 
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
-                mUserPresentReceiver,
-                new IntentFilter(Intent.ACTION_USER_PRESENT),
-                null /* broadcastPermission */,
-                null /* scheduler */);
-
-        // Listen to package add and removal events for all users.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-
-        // Listen to lockdown VPN reset.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+        mUserAllContext.registerReceiver(mIntentReceiver, intentFilter,
+                null /* broadcastPermission */, mHandler);
 
         mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS);
 
@@ -1250,21 +1231,29 @@
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
         netCap.setSingleUid(uid);
         return netCap;
     }
 
+    private NetworkRequest createDefaultRequest() {
+        return createDefaultInternetRequestForTransport(
+                TYPE_NONE, NetworkRequest.Type.REQUEST);
+    }
+
     private NetworkRequest createDefaultInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
-        if (transportType > -1) {
+        if (transportType > TYPE_NONE) {
             netCap.addTransportType(transportType);
         }
+        return createNetworkRequest(type, netCap);
+    }
+
+    private NetworkRequest createNetworkRequest(
+            NetworkRequest.Type type, NetworkCapabilities netCap) {
         return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
     }
 
@@ -1314,7 +1303,8 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    null, networkRequest, new Binder(), null /* attributionTag */));
+                    networkRequest, null, new Binder(),
+                    null /* attributionTags */));
         } else {
             handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
                     /* callOnUnavailable */ false);
@@ -1387,9 +1377,7 @@
     }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
-        synchronized (mVpns) {
-            if (mLockdownEnabled) return null;
-        }
+        if (mLockdownEnabled) return null;
         final NetworkAgentInfo nai = getVpnForUid(uid);
         if (nai != null) return nai.declaredUnderlyingNetworks;
         return null;
@@ -1474,11 +1462,9 @@
         if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
             networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                mLockdownTracker.augmentNetworkInfo(networkInfo);
-            }
-        }
+        networkInfo.setDetailedState(
+                getLegacyLockdownState(networkInfo.getDetailedState()),
+                "" /* reason */, null /* extraInfo */);
     }
 
     /**
@@ -1537,14 +1523,6 @@
         return nai.network;
     }
 
-    // Public because it's used by mLockdownTracker.
-    public NetworkInfo getActiveNetworkInfoUnfiltered() {
-        enforceAccessPermission();
-        final int uid = mDeps.getCallingUid();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return state.networkInfo;
-    }
-
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         NetworkStack.checkNetworkStackPermission(mContext);
@@ -2166,22 +2144,6 @@
                 isBackgroundRestricted);
     }
 
-    /**
-     * Require that the caller is either in the same user or has appropriate permission to interact
-     * across users.
-     *
-     * @param userId Target user for whatever operation the current IPC is supposed to perform.
-     */
-    private void enforceCrossUserPermission(int userId) {
-        if (userId == UserHandle.getCallingUserId()) {
-            // Not a cross-user call.
-            return;
-        }
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                "ConnectivityService");
-    }
-
     private boolean checkAnyPermissionOf(String... permissions) {
         for (String permission : permissions) {
             if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
@@ -2262,12 +2224,6 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
     }
 
-    private void enforceControlAlwaysOnVpnPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
-                "ConnectivityService");
-    }
-
     private void enforceNetworkStackOrSettingsPermission() {
         enforceAnyPermissionOf(
                 android.Manifest.permission.NETWORK_SETTINGS,
@@ -2292,6 +2248,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private void enforceOemNetworkPreferencesPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE,
+                "ConnectivityService");
+    }
+
     private boolean checkNetworkStackPermission() {
         return checkAnyPermissionOf(
                 android.Manifest.permission.NETWORK_STACK,
@@ -2340,13 +2302,6 @@
     }
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                info = new NetworkInfo(info);
-                mLockdownTracker.augmentNetworkInfo(info);
-            }
-        }
-
         Intent intent = new Intent(bcastType);
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -2440,10 +2395,6 @@
             }
         }
 
-        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
-        // for user to unlock device too.
-        updateLockdownVpn();
-
         // Create network requests for always-on networks.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
     }
@@ -2634,6 +2585,12 @@
         }
         pw.println();
 
+        pw.print("Current per-app default networks: ");
+        pw.increaseIndent();
+        dumpPerAppNetworkPreferences(pw);
+        pw.decreaseIndent();
+        pw.println();
+
         pw.println("Current Networks:");
         pw.increaseIndent();
         dumpNetworks(pw);
@@ -2754,6 +2711,40 @@
         }
     }
 
+    private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) {
+        pw.println("Per-App Network Preference:");
+        pw.increaseIndent();
+        if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) {
+            pw.println("none");
+        } else {
+            pw.println(mOemNetworkPreferences.toString());
+        }
+        pw.decreaseIndent();
+
+        for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) {
+            if (mDefaultRequest == defaultRequest) {
+                continue;
+            }
+
+            final boolean isActive = null != defaultRequest.getSatisfier();
+            pw.println("Is per-app network active:");
+            pw.increaseIndent();
+            pw.println(isActive);
+            if (isActive) {
+                pw.println("Active network: " + defaultRequest.getSatisfier().network.netId);
+            }
+            pw.println("Tracked UIDs:");
+            pw.increaseIndent();
+            if (0 == defaultRequest.mRequests.size()) {
+                pw.println("none, this should never occur.");
+            } else {
+                pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids());
+            }
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
     private void dumpNetworkRequests(IndentingPrintWriter pw) {
         for (NetworkRequestInfo nri : requestsSortedById()) {
             pw.println(nri.toString());
@@ -2887,7 +2878,15 @@
                         Log.wtf(TAG, "Non-virtual networks cannot have underlying networks");
                         break;
                     }
+
                     final List<Network> underlying = (List<Network>) arg.second;
+
+                    if (isLegacyLockdownNai(nai)
+                            && (underlying == null || underlying.size() != 1)) {
+                        Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString()
+                                + " must have exactly one underlying network: " + underlying);
+                    }
+
                     final Network[] oldUnderlying = nai.declaredUnderlyingNetworks;
                     nai.declaredUnderlyingNetworks = (underlying != null)
                             ? underlying.toArray(new Network[0]) : null;
@@ -3496,7 +3495,6 @@
                     //  incorrect) behavior.
                     mNetworkActivityTracker.updateDataActivityTracking(
                             null /* newNetwork */, nai);
-                    notifyLockdownVpn(nai);
                     ensureNetworkTransitionWakelock(nai.toShortString());
                 }
             }
@@ -3586,29 +3584,38 @@
     }
 
     private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+        handleRegisterNetworkRequest(Collections.singletonList(nri));
+    }
+
+    private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
-        mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        for (final NetworkRequest req : nri.mRequests) {
-            mNetworkRequests.put(req, nri);
-            if (req.isListen()) {
-                for (final NetworkAgentInfo network : mNetworkAgentInfos) {
-                    if (req.networkCapabilities.hasSignalStrength()
-                            && network.satisfiesImmutableCapabilitiesOf(req)) {
-                        updateSignalStrengthThresholds(network, "REGISTER", req);
+        for (final NetworkRequestInfo nri : nris) {
+            mNetworkRequestInfoLogs.log("REGISTER " + nri);
+            for (final NetworkRequest req : nri.mRequests) {
+                mNetworkRequests.put(req, nri);
+                if (req.isListen()) {
+                    for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+                        if (req.networkCapabilities.hasSignalStrength()
+                                && network.satisfiesImmutableCapabilitiesOf(req)) {
+                            updateSignalStrengthThresholds(network, "REGISTER", req);
+                        }
                     }
                 }
             }
         }
-        rematchAllNetworksAndRequests();
-        // If the nri is satisfied, return as its score has already been sent if needed.
-        if (nri.isBeingSatisfied()) {
-            return;
-        }
 
-        // As this request was not satisfied on rematch and thus never had any scores sent to the
-        // factories, send null now for each request of type REQUEST.
-        for (final NetworkRequest req : nri.mRequests) {
-            if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+        rematchAllNetworksAndRequests();
+        for (final NetworkRequestInfo nri : nris) {
+            // If the nri is satisfied, return as its score has already been sent if needed.
+            if (nri.isBeingSatisfied()) {
+                return;
+            }
+
+            // As this request was not satisfied on rematch and thus never had any scores sent to
+            // the factories, send null now for each request of type REQUEST.
+            for (final NetworkRequest req : nri.mRequests) {
+                if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+            }
         }
     }
 
@@ -3781,6 +3788,7 @@
                 removeListenRequestFromNetworks(req);
             }
         }
+        mDefaultNetworkRequests.remove(nri);
         mNetworkRequestCounter.decrementCount(nri.mUid);
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
 
@@ -4419,6 +4427,16 @@
                 case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
                     handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
                     break;
+                case EVENT_SET_OEM_NETWORK_PREFERENCE:
+                    final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
+                            (Pair<OemNetworkPreferences,
+                                    IOnSetOemNetworkPreferenceListener>) msg.obj;
+                    try {
+                        handleSetOemNetworkPreference(arg.first, arg.second);
+                    } catch (RemoteException e) {
+                        loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+                    }
+                    break;
             }
         }
     }
@@ -4721,183 +4739,6 @@
     }
 
     /**
-     * Prepare for a VPN application.
-     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
-     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param oldPackage Package name of the application which currently controls VPN, which will
-     *                   be replaced. If there is no such application, this should should either be
-     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
-     * @param newPackage Package name of the application which should gain control of VPN, or
-     *                   {@code null} to disable.
-     * @param userId User for whom to prepare the new VPN.
-     *
-     * @hide
-     */
-    @Override
-    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
-            int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
-     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
-     * class. If the caller is not {@code userId}, {@link
-     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param packageName The package for which authorization state should change.
-     * @param userId User for whom {@code packageName} is installed.
-     * @param authorized {@code true} if this app should be able to start a VPN connection without
-     *     explicit user approval, {@code false} if not.
-     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
-     *     permissions should be granted. When unauthorizing an app, {@link
-     *     VpnManager.TYPE_VPN_NONE} should be used.
-     * @hide
-     */
-    @Override
-    public void setVpnPackageAuthorization(
-            String packageName, int userId, @VpnManager.VpnType int vpnType) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                vpn.setPackageAuthorization(packageName, vpnType);
-            }
-        }
-    }
-
-    /**
-     * Configure a TUN interface and return its file descriptor. Parameters
-     * are encoded and opaque to this class. This method is used by VpnBuilder
-     * and not available in ConnectivityManager. Permissions are checked in
-     * Vpn class.
-     * @hide
-     */
-    @Override
-    public ParcelFileDescriptor establishVpn(VpnConfig config) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).establish(config);
-        }
-    }
-
-    /**
-     * Stores the given VPN profile based on the provisioning package name.
-     *
-     * <p>If there is already a VPN profile stored for the provisioning package, this call will
-     * overwrite the profile.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @return {@code true} if user consent has already been granted, {@code false} otherwise.
-     * @hide
-     */
-    @Override
-    public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
-        }
-    }
-
-    /**
-     * Deletes the stored VPN profile for the provisioning package
-     *
-     * <p>If there are no profiles for the given package, this method will silently succeed.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void deleteVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Starts the VPN based on the stored profile for the given package
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @throws IllegalArgumentException if no profile was found for the given package name.
-     * @hide
-     */
-    @Override
-    public void startVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Stops the Platform VPN if the provided package is running one.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void stopVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).stopVpnProfile(packageName);
-        }
-    }
-
-    /**
-     * Start legacy VPN, controlling native daemons as needed. Creates a
-     * secondary thread to perform connection work, returning quickly.
-     */
-    @Override
-    public void startLegacyVpn(VpnProfile profile) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final LinkProperties egress = getActiveLinkProperties();
-        if (egress == null) {
-            throw new IllegalStateException("Missing active network connection");
-        }
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
-        }
-    }
-
-    /**
-     * Return the information of the ongoing legacy VPN. This method is used
-     * by VpnSettings and not available in ConnectivityManager. Permissions
-     * are checked in Vpn class.
-     */
-    @Override
-    public LegacyVpnInfo getLegacyVpnInfo(int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            return mVpns.get(userId).getLegacyVpnInfo();
-        }
-    }
-
-    /**
      * Return the information of all ongoing VPNs.
      *
      * <p>This method is used to update NetworkStatsService.
@@ -4906,10 +4747,8 @@
      */
     private UnderlyingNetworkInfo[] getAllVpnInfo() {
         ensureRunningOnConnectivityServiceThread();
-        synchronized (mVpns) {
-            if (mLockdownEnabled) {
-                return new UnderlyingNetworkInfo[0];
-            }
+        if (mLockdownEnabled) {
+            return new UnderlyingNetworkInfo[0];
         }
         List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
@@ -4965,25 +4804,6 @@
                 nai.linkProperties.getInterfaceName(), interfaces);
     }
 
-    /**
-     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
-     * VpnDialogs and not available in ConnectivityManager.
-     * Permissions are checked in Vpn class.
-     * @hide
-     */
-    @Override
-    public VpnConfig getVpnConfig(int userId) {
-        enforceCrossUserPermission(userId);
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.getVpnConfig();
-            } else {
-                return null;
-            }
-        }
-    }
-
     // TODO This needs to be the default network that applies to the NAI.
     private Network[] underlyingNetworksOrDefault(final int ownerUid,
             Network[] underlyingNetworks) {
@@ -5071,195 +4891,54 @@
         mVpnBlockedUidRanges = newVpnBlockedUidRanges;
     }
 
-    private boolean isLockdownVpnEnabled() {
-        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
-    }
-
     @Override
-    public boolean updateLockdownVpn() {
-        // Allow the system UID for the system server and for Settings.
-        // Also, for unit tests, allow the process that ConnectivityService is running in.
-        if (mDeps.getCallingUid() != Process.SYSTEM_UID
-                && Binder.getCallingPid() != Process.myPid()) {
-            logw("Lockdown VPN only available to system process or AID_SYSTEM");
-            return false;
-        }
-
-        synchronized (mVpns) {
-            // Tear down existing lockdown if profile was removed
-            mLockdownEnabled = isLockdownVpnEnabled();
-            if (mLockdownEnabled) {
-                byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
-                if (profileTag == null) {
-                    loge("Lockdown VPN configured but cannot be read from keystore");
-                    return false;
-                }
-                String profileName = new String(profileTag);
-                final VpnProfile profile = VpnProfile.decode(
-                        profileName, mKeyStore.get(Credentials.VPN + profileName));
-                if (profile == null) {
-                    loge("Lockdown VPN configured invalid profile " + profileName);
-                    setLockdownTracker(null);
-                    return true;
-                }
-                int user = UserHandle.getUserId(mDeps.getCallingUid());
-                Vpn vpn = mVpns.get(user);
-                if (vpn == null) {
-                    logw("VPN for user " + user + " not ready yet. Skipping lockdown");
-                    return false;
-                }
-                setLockdownTracker(
-                        new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn,  profile));
-            } else {
-                setLockdownTracker(null);
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
-     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
-     */
-    @GuardedBy("mVpns")
-    private void setLockdownTracker(LockdownVpnTracker tracker) {
-        // Shutdown any existing tracker
-        final LockdownVpnTracker existing = mLockdownTracker;
-        // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
-        // necessary onBlockedStatusChanged callbacks.
-        mLockdownTracker = null;
-        if (existing != null) {
-            existing.shutdown();
-        }
-
-        if (tracker != null) {
-            mLockdownTracker = tracker;
-            mLockdownTracker.init();
-        }
-    }
-
-    /**
-     * Throws if there is any currently running, always-on Legacy VPN.
-     *
-     * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
-     * running across the entire system. Tracking for app-based VPNs is done on a per-user,
-     * per-package basis in Vpn.java
-     */
-    @GuardedBy("mVpns")
-    private void throwIfLockdownEnabled() {
-        if (mLockdownEnabled) {
-            throw new IllegalStateException("Unavailable in lockdown mode");
-        }
-    }
-
-    /**
-     * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
-     * some setup and then call {@code establish()} to connect.
-     *
-     * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
-     */
-    private boolean startAlwaysOnVpn(int userId) {
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                // Shouldn't happen as all code paths that point here should have checked the Vpn
-                // exists already.
-                Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
-                return false;
-            }
-
-            return vpn.startAlwaysOnVpn(mKeyStore);
-        }
-    }
-
-    @Override
-    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
         enforceSettingsPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
-        }
+        mHandler.post(() -> mLockdownEnabled = enabled);
     }
 
-    @Override
-    public boolean setAlwaysOnVpnPackage(
-            int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
-            if (isLockdownVpnEnabled()) {
-                return false;
-            }
-
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
-                return false;
-            }
-            if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-                return false;
-            }
-        }
-        return true;
+    private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
+        return mLockdownEnabled
+                && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
+                && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
     }
 
-    @Override
-    public String getAlwaysOnVpnPackage(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getAlwaysOnPackage();
+    private NetworkAgentInfo getLegacyLockdownNai() {
+        if (!mLockdownEnabled) {
+            return null;
         }
-    }
+        // The legacy lockdown VPN always only applies to userId 0.
+        final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
+        if (nai == null || !isLegacyLockdownNai(nai)) return null;
 
-    @Override
-    public boolean isVpnLockdownEnabled(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.getLockdown();
+        // The legacy lockdown VPN must always have exactly one underlying network.
+        // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
+        // a local variable. There is no need to make a copy because its contents cannot change.
+        final Network[] underlying = nai.declaredUnderlyingNetworks;
+        if (underlying == null ||  underlying.length != 1) {
+            return null;
         }
-    }
 
-    @Override
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getLockdownAllowlist();
+        // The legacy lockdown VPN always uses the default network.
+        // If the VPN's underlying network is no longer the current default network, it means that
+        // the default network has just switched, and the VPN is about to disconnect.
+        // Report that the VPN is not connected, so when the state of NetworkInfo objects
+        // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED.
+        final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+        if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
+            return null;
         }
+
+        return nai;
+    };
+
+    private DetailedState getLegacyLockdownState(DetailedState origState) {
+        if (origState != DetailedState.CONNECTED) {
+            return origState;
+        }
+        return (mLockdownEnabled && getLegacyLockdownNai() == null)
+                ? DetailedState.CONNECTING
+                : DetailedState.CONNECTED;
     }
 
     @Override
@@ -5294,111 +4973,12 @@
         }
     }
 
-    private void onUserStarted(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn != null) {
-                loge("Starting user already has a VPN");
-                return;
-            }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
-            mVpns.put(userId, userVpn);
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            }
-        }
-    }
-
-    private void onUserStopped(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn == null) {
-                loge("Stopped user has no VPN");
-                return;
-            }
-            userVpn.onUserStopped();
-            mVpns.delete(userId);
-        }
-    }
-
     private void onUserAdded(int userId) {
         mPermissionMonitor.onUserAdded(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserAdded(userId);
-            }
-        }
     }
 
     private void onUserRemoved(int userId) {
         mPermissionMonitor.onUserRemoved(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserRemoved(userId);
-            }
-        }
-    }
-
-    private void onPackageReplaced(String packageName, int uid) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
-            return;
-        }
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
-                log("Restarting always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.startAlwaysOnVpn(mKeyStore);
-            }
-        }
-    }
-
-    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
-            return;
-        }
-
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
-                log("Removing always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-            }
-        }
-    }
-
-    private void onUserUnlocked(int userId) {
-        synchronized (mVpns) {
-            // User present may be sent because of an unlock, which might mean an unlocked keystore.
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            } else {
-                startAlwaysOnVpn(userId);
-            }
-        }
-    }
-
-    private void onVpnLockdownReset() {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) mLockdownTracker.reset();
-        }
     }
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -5407,52 +4987,20 @@
             ensureRunningOnConnectivityServiceThread();
             final String action = intent.getAction();
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            final Uri packageData = intent.getData();
-            final String packageName =
-                    packageData != null ? packageData.getSchemeSpecificPart() : null;
-
-            if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
-                onVpnLockdownReset();
-            }
 
             // UserId should be filled for below intents, check the existence.
             if (userId == UserHandle.USER_NULL) return;
 
-            if (Intent.ACTION_USER_STARTED.equals(action)) {
-                onUserStarted(userId);
-            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                onUserStopped(userId);
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
                 onUserAdded(userId);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 onUserRemoved(userId);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                onUserUnlocked(userId);
-            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                onPackageReplaced(packageName, uid);
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                final boolean isReplacing = intent.getBooleanExtra(
-                        Intent.EXTRA_REPLACING, false);
-                onPackageRemoved(packageName, uid, isReplacing);
-            } else {
+            }  else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
         }
     };
 
-    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // Try creating lockdown tracker, since user present usually means
-            // unlocked keystore.
-            updateLockdownVpn();
-            // Use the same context that registered receiver before to unregister it. Because use
-            // different context to unregister receiver will cause exception.
-            context.unregisterReceiver(this);
-        }
-    };
-
     private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
@@ -5551,10 +5099,12 @@
 
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
+        @Nullable
+        final Messenger mMessenger;
+        @Nullable
         private final IBinder mBinder;
         final int mPid;
         final int mUid;
-        final Messenger messenger;
         @Nullable
         final String mCallingAttributionTag;
 
@@ -5570,12 +5120,17 @@
             return uids;
         }
 
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi,
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
                 @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), pi, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+                @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mPendingIntent = pi;
-            messenger = null;
+            mMessenger = null;
             mBinder = null;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
@@ -5583,11 +5138,16 @@
             mCallingAttributionTag = callingAttributionTag;
         }
 
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder,
-                @Nullable String callingAttributionTag) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), m, binder, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
             super();
-            messenger = m;
             mRequests = initializeRequests(r);
+            mMessenger = m;
             ensureAllNetworkRequestsHaveType(mRequests);
             mBinder = binder;
             mPid = getCallingPid();
@@ -5603,7 +5163,11 @@
             }
         }
 
-        NetworkRequestInfo(NetworkRequest r) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r) {
+            this(Collections.singletonList(r));
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
             this(r, null /* pi */, null /* callingAttributionTag */);
         }
 
@@ -5618,9 +5182,10 @@
             return mRequests.size() > 1;
         }
 
-        private List<NetworkRequest> initializeRequests(NetworkRequest r) {
-            final ArrayList<NetworkRequest> tempRequests = new ArrayList<>();
-            tempRequests.add(new NetworkRequest(r));
+        private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) {
+            // Creating a defensive copy to prevent the sender from modifying the list being
+            // reflected in the return value of this method.
+            final List<NetworkRequest> tempRequests = new ArrayList<>(r);
             return Collections.unmodifiableList(tempRequests);
         }
 
@@ -5804,7 +5369,7 @@
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
         NetworkRequestInfo nri =
-                new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
         // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
@@ -5813,7 +5378,8 @@
         // changes don't alter request matching.
         if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
                 (!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
-            Log.wtf(TAG, "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+            throw new IllegalStateException(
+                    "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
                     + networkCapabilities + " vs. " + defaultNc);
         }
 
@@ -5970,7 +5536,7 @@
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
         NetworkRequestInfo nri =
-                new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
         if (VDBG) log("listenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -6098,13 +5664,20 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
+    // Current OEM network preferences.
+    @NonNull
+    private OemNetworkPreferences mOemNetworkPreferences =
+            new OemNetworkPreferences.Builder().build();
+
     // The always-on request for an Internet-capable network that apps without a specific default
     // fall back to.
+    @VisibleForTesting
     @NonNull
-    private final NetworkRequestInfo mDefaultRequest;
+    final NetworkRequestInfo mDefaultRequest;
     // Collection of NetworkRequestInfo's used for default networks.
+    @VisibleForTesting
     @NonNull
-    private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
+    final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
 
     private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
         return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
@@ -7181,7 +6754,7 @@
     private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
             @NonNull final NetworkAgentInfo networkAgent, final int notificationType,
             final int arg1) {
-        if (nri.messenger == null) {
+        if (nri.mMessenger == null) {
             // Default request has no msgr. Also prevents callbacks from being invoked for
             // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
             // are Type.LISTEN, but should not have NetworkCallbacks invoked.
@@ -7250,7 +6823,7 @@
                 String notification = ConnectivityManager.getCallbackName(notificationType);
                 log("sending notification " + notification + " for " + nrForCallback);
             }
-            nri.messenger.send(msg);
+            nri.mMessenger.send(msg);
         } catch (RemoteException e) {
             // may occur naturally in the race of binder death.
             loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
@@ -7339,7 +6912,6 @@
             mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
         }
         mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
-        notifyLockdownVpn(newDefaultNetwork);
         handleApplyDefaultProxy(null != newDefaultNetwork
                 ? newDefaultNetwork.linkProperties.getHttpProxy() : null);
         updateTcpBufferSizes(null != newDefaultNetwork
@@ -7797,12 +7369,6 @@
                 mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
                 mLegacyTypeTracker.add(
                         newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
-                // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
-                // to reflect the NetworkInfo of this new network. This broadcast has to be sent
-                // after the disconnect broadcasts above, but before the broadcasts sent by the
-                // legacy type tracker below.
-                // TODO : refactor this, it's too complex
-                notifyLockdownVpn(newDefaultNetwork);
             }
         }
 
@@ -7860,18 +7426,6 @@
         sendInetConditionBroadcast(nai.networkInfo);
     }
 
-    private void notifyLockdownVpn(NetworkAgentInfo nai) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                if (nai != null && nai.isVPN()) {
-                    mLockdownTracker.onVpnStateChanged(nai.networkInfo);
-                } else {
-                    mLockdownTracker.onNetworkInfoChanged();
-                }
-            }
-        }
-    }
-
     @NonNull
     private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
         final NetworkInfo newInfo = new NetworkInfo(info);
@@ -7910,7 +7464,6 @@
             oldInfo = networkAgent.networkInfo;
             networkAgent.networkInfo = newInfo;
         }
-        notifyLockdownVpn(networkAgent);
 
         if (DBG) {
             log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
@@ -8211,34 +7764,6 @@
     }
 
     @Override
-    public boolean addVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).addAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean removeVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).removeAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final boolean success;
-        synchronized (mVpns) {
-            success = mVpns.get(user).setUnderlyingNetworks(networks);
-        }
-        return success;
-    }
-
-    @Override
     public String getCaptivePortalServerUrl() {
         enforceNetworkStackOrSettingsPermission();
         String settingUrl = mContext.getResources().getString(
@@ -8317,8 +7842,6 @@
             return;
         }
 
-        final int userId = UserHandle.getCallingUserId();
-
         final long token = Binder.clearCallingIdentity();
         try {
             final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
@@ -8330,44 +7853,6 @@
         // Turn airplane mode off
         setAirplaneMode(false);
 
-        if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
-            // Remove always-on package
-            synchronized (mVpns) {
-                final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
-                if (alwaysOnPackage != null) {
-                    setAlwaysOnVpnPackage(userId, null, false, null);
-                    setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
-                }
-
-                // Turn Always-on VPN off
-                if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        mKeyStore.delete(Credentials.LOCKDOWN_VPN);
-                        mLockdownEnabled = false;
-                        setLockdownTracker(null);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-
-                // Turn VPN off
-                VpnConfig vpnConfig = getVpnConfig(userId);
-                if (vpnConfig != null) {
-                    if (vpnConfig.legacy) {
-                        prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
-                    } else {
-                        // Prevent this app (packagename = vpnConfig.user) from initiating
-                        // VPN connections in the future without user intervention.
-                        setVpnPackageAuthorization(
-                                vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
-
-                        prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
-                    }
-                }
-            }
-        }
-
         // restore private DNS settings to default mode (opportunistic)
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
             Settings.Global.putString(mContext.getContentResolver(),
@@ -8459,25 +7944,6 @@
         }
     }
 
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner() {
-        return getVpnIfOwner(mDeps.getCallingUid());
-    }
-
-    // TODO: stop calling into Vpn.java and get this information from data in this class.
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner(int uid) {
-        final int user = UserHandle.getUserId(uid);
-
-        final Vpn vpn = mVpns.get(user);
-        if (vpn == null) {
-            return null;
-        } else {
-            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
-            return (info == null || info.ownerUid != uid) ? null : vpn;
-        }
-    }
-
     private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
         if (vpn == null) return VpnManager.TYPE_VPN_NONE;
         final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
@@ -8514,22 +7980,6 @@
         return uid;
     }
 
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getAlwaysOn();
-        }
-    }
-
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getLockdown();
-        }
-    }
-
     /**
      * Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
      *
@@ -9205,9 +8655,212 @@
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
+    private void enforceAutomotiveDevice() {
+        final boolean isAutomotiveDevice =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        if (!isAutomotiveDevice) {
+            throw new UnsupportedOperationException(
+                    "setOemNetworkPreference() is only available on automotive devices.");
+        }
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used
+     * to communicate completion of setOemNetworkPreference();
+     */
     @Override
-    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        // TODO http://b/176495594 track multiple default networks with networkPreferences
-        if (DBG) log("setOemNetworkPreference() called with: " + preference.toString());
+    public void setOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+
+        enforceAutomotiveDevice();
+        enforceOemNetworkPreferencesPermission();
+
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        validateOemNetworkPreferences(preference);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
+                new Pair<>(preference, listener)));
+    }
+
+    private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) {
+        for (@OemNetworkPreferences.OemNetworkPreference final int pref
+                : preference.getNetworkPreferences().values()) {
+            if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) {
+                final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value.";
+                throw new IllegalArgumentException(msg);
+            }
+        }
+    }
+
+    private void handleSetOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (DBG) {
+            log("set OEM network preferences :" + preference.toString());
+        }
+        final List<NetworkRequestInfo> nris =
+                new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
+        updateDefaultNetworksForOemNetworkPreference(nris);
+        mOemNetworkPreferences = preference;
+        // TODO http://b/176496396 persist data to shared preferences.
+
+        if (null != listener) {
+            listener.onComplete();
+        }
+    }
+
+    private void updateDefaultNetworksForOemNetworkPreference(
+            @NonNull final List<NetworkRequestInfo> nris) {
+        ensureRunningOnConnectivityServiceThread();
+        clearNonDefaultNetworkAgents();
+        addDefaultNetworkRequests(nris);
+    }
+
+    private void clearNonDefaultNetworkAgents() {
+        // Copy mDefaultNetworkRequests to iterate and remove elements from it in
+        // handleRemoveNetworkRequest() without getting a ConcurrentModificationException.
+        final NetworkRequestInfo[] nris =
+                mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]);
+        for (final NetworkRequestInfo nri : nris) {
+            if (mDefaultRequest != nri) {
+                handleRemoveNetworkRequest(nri);
+            }
+        }
+    }
+
+    private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) {
+        mDefaultNetworkRequests.addAll(nris);
+        handleRegisterNetworkRequest(nris);
+    }
+
+    /**
+     * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
+     */
+    @VisibleForTesting
+    final class OemNetworkRequestFactory {
+        List<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final List<NetworkRequestInfo> nris = new ArrayList<>();
+            final SparseArray<Set<Integer>> uids =
+                    createUidsFromOemNetworkPreferences(preference);
+            for (int i = 0; i < uids.size(); i++) {
+                final int key = uids.keyAt(i);
+                final Set<Integer> value = uids.valueAt(i);
+                final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value);
+                // No need to add an nri without any requests.
+                if (0 == nri.mRequests.size()) {
+                    continue;
+                }
+                nris.add(nri);
+            }
+
+            return nris;
+        }
+
+        private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final SparseArray<Set<Integer>> uids = new SparseArray<>();
+            final PackageManager pm = mContext.getPackageManager();
+            for (final Map.Entry<String, Integer> entry :
+                    preference.getNetworkPreferences().entrySet()) {
+                @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
+                try {
+                    final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid;
+                    if (!uids.contains(pref)) {
+                        uids.put(pref, new ArraySet<>());
+                    }
+                    uids.get(pref).add(uid);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // Although this may seem like an error scenario, it is ok that uninstalled
+                    // packages are sent on a network preference as the system will watch for
+                    // package installations associated with this network preference and update
+                    // accordingly. This is done so as to minimize race conditions on app install.
+                    // TODO b/177092163 add app install watching.
+                    continue;
+                }
+            }
+            return uids;
+        }
+
+        private NetworkRequestInfo createNriFromOemNetworkPreferences(
+                @OemNetworkPreferences.OemNetworkPreference final int preference,
+                @NonNull final Set<Integer> uids) {
+            final List<NetworkRequest> requests = new ArrayList<>();
+            // Requests will ultimately be evaluated by order of insertion therefore it matters.
+            switch (preference) {
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    requests.add(createDefaultRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY:
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY:
+                    requests.add(createOemPrivateNetworkRequest());
+                    break;
+                default:
+                    // This should never happen.
+                    throw new IllegalArgumentException("createNriFromOemNetworkPreferences()"
+                            + " called with invalid preference of " + preference);
+            }
+
+            setOemNetworkRequestUids(requests, uids);
+            return new NetworkRequestInfo(requests);
+        }
+
+        private NetworkRequest createUnmeteredNetworkRequest() {
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_NOT_METERED)
+                    .addCapability(NET_CAPABILITY_VALIDATED);
+            return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap);
+        }
+
+        private NetworkRequest createOemPaidNetworkRequest() {
+            // NET_CAPABILITY_OEM_PAID is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PAID)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkRequest createOemPrivateNetworkRequest() {
+            // NET_CAPABILITY_OEM_PRIVATE is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PRIVATE)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkCapabilities createDefaultPerAppNetCap() {
+            final NetworkCapabilities netCap = new NetworkCapabilities();
+            netCap.addCapability(NET_CAPABILITY_INTERNET);
+            netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+            return netCap;
+        }
+
+        private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
+                @NonNull final Set<Integer> uids) {
+            final Set<UidRange> ranges = new ArraySet<>();
+            for (final int uid : uids) {
+                ranges.add(new UidRange(uid, uid));
+            }
+            for (final NetworkRequest req : requests) {
+                req.networkCapabilities.setUids(ranges);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index f648c3e..b48bc90 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -29,6 +29,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.InetAddresses;
@@ -41,6 +42,7 @@
 import android.net.IpSecTunnelInterfaceResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.TrafficStats;
 import android.net.util.NetdService;
@@ -797,9 +799,15 @@
         }
     }
 
-    private final class TunnelInterfaceRecord extends OwnedResourceRecord {
+    /**
+     * Tracks an tunnel interface, and manages cleanup paths.
+     *
+     * <p>This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock
+     */
+    @VisibleForTesting
+    final class TunnelInterfaceRecord extends OwnedResourceRecord {
         private final String mInterfaceName;
-        private final Network mUnderlyingNetwork;
 
         // outer addresses
         private final String mLocalAddress;
@@ -810,6 +818,8 @@
 
         private final int mIfId;
 
+        private Network mUnderlyingNetwork;
+
         TunnelInterfaceRecord(
                 int resourceId,
                 String interfaceName,
@@ -870,14 +880,22 @@
             releaseNetId(mOkey);
         }
 
-        public String getInterfaceName() {
-            return mInterfaceName;
+        @GuardedBy("IpSecService.this")
+        public void setUnderlyingNetwork(Network underlyingNetwork) {
+            // When #applyTunnelModeTransform is called, this new underlying network will be used to
+            // update the output mark of the input transform.
+            mUnderlyingNetwork = underlyingNetwork;
         }
 
+        @GuardedBy("IpSecService.this")
         public Network getUnderlyingNetwork() {
             return mUnderlyingNetwork;
         }
 
+        public String getInterfaceName() {
+            return mInterfaceName;
+        }
+
         /** Returns the local, outer address for the tunnelInterface */
         public String getLocalAddress() {
             return mLocalAddress;
@@ -1429,6 +1447,34 @@
         }
     }
 
+    /** Set TunnelInterface to use a specific underlying network. */
+    @Override
+    public synchronized void setNetworkForTunnelInterface(
+            int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+        final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        final ConnectivityManager connectivityManager =
+                mContext.getSystemService(ConnectivityManager.class);
+        final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+        if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+            throw new IllegalArgumentException(
+                    "Underlying network cannot be the network being exposed by this tunnel");
+        }
+
+        // It is meaningless to check if the network exists or is valid because the network might
+        // disconnect at any time after it passes the check.
+
+        tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+    }
+
     /**
      * Delete a TunnelInterface that has been been allocated by and registered with the system
      * server
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c6a8660..e12586b 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,8 +1,8 @@
 # Connectivity / Networking
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
 
-# Vibrator / Threads
-per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+# Threads
+per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 41903fc..67f6ec9 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -902,10 +902,13 @@
                 if (registeredObserver != null) {
                     Iterator<MonitoredPackage> it = failedPackages.iterator();
                     while (it.hasNext()) {
-                        VersionedPackage versionedPkg = it.next().mPackage;
-                        Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
-                        registeredObserver.execute(versionedPkg,
-                                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+                        VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
+                        if (versionedPkg != null) {
+                            Slog.i(TAG,
+                                    "Explicit health check failed for package " + versionedPkg);
+                            registeredObserver.execute(versionedPkg,
+                                    PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+                        }
                     }
                 }
             }
@@ -1342,11 +1345,7 @@
 
     MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
             boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
-        VersionedPackage pkg = getVersionedPackage(name);
-        if (pkg == null) {
-            return null;
-        }
-        return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs,
+        return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
                 hasPassedHealthCheck, mitigationCalls);
     }
 
@@ -1371,7 +1370,7 @@
      * instances of this class.
      */
     class MonitoredPackage {
-        private final VersionedPackage mPackage;
+        private final String mPackageName;
         // Times when package failures happen sorted in ascending order
         @GuardedBy("mLock")
         private final LongArrayQueue mFailureHistory = new LongArrayQueue();
@@ -1399,10 +1398,10 @@
         @GuardedBy("mLock")
         private long mHealthCheckDurationMs = Long.MAX_VALUE;
 
-        MonitoredPackage(VersionedPackage pkg, long durationMs,
+        MonitoredPackage(String packageName, long durationMs,
                 long healthCheckDurationMs, boolean hasPassedHealthCheck,
                 LongArrayQueue mitigationCalls) {
-            mPackage = pkg;
+            mPackageName = packageName;
             mDurationMs = durationMs;
             mHealthCheckDurationMs = healthCheckDurationMs;
             mHasPassedHealthCheck = hasPassedHealthCheck;
@@ -1556,7 +1555,7 @@
 
         /** Returns the monitored package name. */
         private String getName() {
-            return mPackage.getPackageName();
+            return mPackageName;
         }
 
         /**
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1ad0176..2f98199 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -939,9 +939,12 @@
         if (transcodeEnabled) {
             LocalServices.getService(ActivityManagerInternal.class)
                     .registerAnrController((packageName, uid) -> {
-                        // TODO: Retrieve delay from ExternalStorageService that can check
-                        // transcoding status
-                        return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0);
+                        try {
+                            return mStorageSessionController.getAnrDelayMillis(packageName, uid);
+                        } catch (ExternalStorageServiceException e) {
+                            Log.e(TAG, "Failed to get ANR delay for " + packageName, e);
+                            return 0;
+                        }
                     });
         }
     }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 7f638b9..915517a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -19,6 +19,7 @@
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
@@ -82,6 +83,7 @@
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -158,6 +160,7 @@
     private NotificationManager mNotificationManager;
     private StatusBarManager mStatusBarManager;
     private WindowManagerInternal mWindowManager;
+    private ActivityTaskManagerInternal mActivityTaskManager;
     private AlarmManager mAlarmManager;
     private PowerManager mPowerManager;
 
@@ -366,6 +369,7 @@
                 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                 mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+                mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
                 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
                 TwilightManager twilightManager = getLocalService(TwilightManager.class);
                 if (twilightManager != null) mTwilightManager = twilightManager;
@@ -750,6 +754,39 @@
         }
 
         @Override
+        public void setApplicationNightMode(@UiModeManager.NightMode int mode)
+                throws RemoteException {
+            switch (mode) {
+                case UiModeManager.MODE_NIGHT_NO:
+                case UiModeManager.MODE_NIGHT_YES:
+                case UiModeManager.MODE_NIGHT_AUTO:
+                case UiModeManager.MODE_NIGHT_CUSTOM:
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown mode: " + mode);
+            }
+            final int configNightMode;
+            switch (mode) {
+                case MODE_NIGHT_YES:
+                    configNightMode = Configuration.UI_MODE_NIGHT_YES;
+                    break;
+                case MODE_NIGHT_NO:
+                    configNightMode = Configuration.UI_MODE_NIGHT_NO;
+                    break;
+                default:
+                    configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
+            }
+            try {
+                final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+                        mActivityTaskManager.createPackageConfigurationUpdater();
+                updater.setNightMode(configNightMode);
+                updater.commit();
+            } catch (RemoteException e) {
+                throw e;
+            }
+        }
+
+        @Override
         public boolean isUiModeLocked() {
             synchronized (mLock) {
                 return mUiModeLocked;
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
new file mode 100644
index 0000000..5d89bf1
--- /dev/null
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.NETWORK_STACK;
+
+import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.IVpnManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkStack;
+import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
+import android.net.VpnManager;
+import android.net.VpnService;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.LockdownVpnTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs.
+ * @hide
+ */
+public class VpnManagerService extends IVpnManager.Stub {
+    private static final String TAG = VpnManagerService.class.getSimpleName();
+
+    @VisibleForTesting
+    protected final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+
+    private final Context mContext;
+    private final Context mUserAllContext;
+
+    private final Dependencies mDeps;
+
+    private final ConnectivityManager mCm;
+    private final KeyStore mKeyStore;
+    private final INetworkManagementService mNMS;
+    private final INetd mNetd;
+    private final UserManager mUserManager;
+
+    @VisibleForTesting
+    @GuardedBy("mVpns")
+    protected final SparseArray<Vpn> mVpns = new SparseArray<>();
+
+    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+    // a direct call to LockdownVpnTracker.isEnabled().
+    @GuardedBy("mVpns")
+    private boolean mLockdownEnabled;
+    @GuardedBy("mVpns")
+    private LockdownVpnTracker mLockdownTracker;
+
+    /**
+     * Dependencies of VpnManager, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Returns the calling UID of an IPC. */
+        public int getCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        /** Creates a HandlerThread to be used by this class. */
+        public HandlerThread makeHandlerThread() {
+            return new HandlerThread("VpnManagerService");
+        }
+
+        /** Returns the KeyStore instance to be used by this class. */
+        public KeyStore getKeyStore() {
+            return KeyStore.getInstance();
+        }
+
+        public INetd getNetd() {
+            return NetdService.getInstance();
+        }
+
+        public INetworkManagementService getINetworkManagementService() {
+            return INetworkManagementService.Stub.asInterface(
+                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+        }
+    }
+
+    public VpnManagerService(Context context, Dependencies deps) {
+        mContext = context;
+        mDeps = deps;
+        mHandlerThread = mDeps.makeHandlerThread();
+        mHandlerThread.start();
+        mHandler = mHandlerThread.getThreadHandler();
+        mKeyStore = mDeps.getKeyStore();
+        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mNMS = mDeps.getINetworkManagementService();
+        mNetd = mDeps.getNetd();
+        mUserManager = mContext.getSystemService(UserManager.class);
+        registerReceivers();
+        log("VpnManagerService starting up");
+    }
+
+    /** Creates a new VpnManagerService */
+    public static VpnManagerService create(Context context) {
+        return new VpnManagerService(context, new Dependencies());
+    }
+
+    /** Informs the service that the system is ready. */
+    public void systemReady() {
+        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
+        // for user to unlock device too.
+        updateLockdownVpn();
+    }
+
+    @Override
+    /** Dumps service state. */
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+            @Nullable String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println("VPNs:");
+        pw.increaseIndent();
+        synchronized (mVpns) {
+            for (int i = 0; i < mVpns.size(); i++) {
+                pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
+     * Prepare for a VPN application.
+     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param oldPackage Package name of the application which currently controls VPN, which will
+     *                   be replaced. If there is no such application, this should should either be
+     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
+     * @param newPackage Package name of the application which should gain control of VPN, or
+     *                   {@code null} to disable.
+     * @param userId User for whom to prepare the new VPN.
+     *
+     * @hide
+     */
+    @Override
+    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+            int userId) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
+     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
+     * class. If the caller is not {@code userId}, {@link
+     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param packageName The package for which authorization state should change.
+     * @param userId User for whom {@code packageName} is installed.
+     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
+     *     permissions should be granted. When unauthorizing an app, {@link
+     *     VpnManager.TYPE_VPN_NONE} should be used.
+     * @hide
+     */
+    @Override
+    public void setVpnPackageAuthorization(
+            String packageName, int userId, @VpnManager.VpnType int vpnType) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                vpn.setPackageAuthorization(packageName, vpnType);
+            }
+        }
+    }
+
+    /**
+     * Configure a TUN interface and return its file descriptor. Parameters
+     * are encoded and opaque to this class. This method is used by VpnBuilder
+     * and not available in VpnManager. Permissions are checked in
+     * Vpn class.
+     * @hide
+     */
+    @Override
+    public ParcelFileDescriptor establishVpn(VpnConfig config) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).establish(config);
+        }
+    }
+
+    @Override
+    public boolean addVpnAddress(String address, int prefixLength) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).addAddress(address, prefixLength);
+        }
+    }
+
+    @Override
+    public boolean removeVpnAddress(String address, int prefixLength) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).removeAddress(address, prefixLength);
+        }
+    }
+
+    @Override
+    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        final boolean success;
+        synchronized (mVpns) {
+            success = mVpns.get(user).setUnderlyingNetworks(networks);
+        }
+        return success;
+    }
+
+    /**
+     * Stores the given VPN profile based on the provisioning package name.
+     *
+     * <p>If there is already a VPN profile stored for the provisioning package, this call will
+     * overwrite the profile.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @return {@code true} if user consent has already been granted, {@code false} otherwise.
+     * @hide
+     */
+    @Override
+    public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
+        }
+    }
+
+    /**
+     * Deletes the stored VPN profile for the provisioning package
+     *
+     * <p>If there are no profiles for the given package, this method will silently succeed.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @hide
+     */
+    @Override
+    public void deleteVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
+        }
+    }
+
+    /**
+     * Starts the VPN based on the stored profile for the given package
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @throws IllegalArgumentException if no profile was found for the given package name.
+     * @hide
+     */
+    @Override
+    public void startVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
+        }
+    }
+
+    /**
+     * Stops the Platform VPN if the provided package is running one.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @hide
+     */
+    @Override
+    public void stopVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            mVpns.get(user).stopVpnProfile(packageName);
+        }
+    }
+
+    /**
+     * Start legacy VPN, controlling native daemons as needed. Creates a
+     * secondary thread to perform connection work, returning quickly.
+     */
+    @Override
+    public void startLegacyVpn(VpnProfile profile) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        final LinkProperties egress = mCm.getActiveLinkProperties();
+        if (egress == null) {
+            throw new IllegalStateException("Missing active network connection");
+        }
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
+        }
+    }
+
+    /**
+     * Return the information of the ongoing legacy VPN. This method is used
+     * by VpnSettings and not available in ConnectivityManager. Permissions
+     * are checked in Vpn class.
+     */
+    @Override
+    public LegacyVpnInfo getLegacyVpnInfo(int userId) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            return mVpns.get(userId).getLegacyVpnInfo();
+        }
+    }
+
+    /**
+     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
+     * VpnDialogs and not available in ConnectivityManager.
+     * Permissions are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public VpnConfig getVpnConfig(int userId) {
+        enforceCrossUserPermission(userId);
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                return vpn.getVpnConfig();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private boolean isLockdownVpnEnabled() {
+        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
+    }
+
+    @Override
+    public boolean updateLockdownVpn() {
+        // Allow the system UID for the system server and for Settings.
+        // Also, for unit tests, allow the process that ConnectivityService is running in.
+        if (mDeps.getCallingUid() != Process.SYSTEM_UID
+                && Binder.getCallingPid() != Process.myPid()) {
+            logw("Lockdown VPN only available to system process or AID_SYSTEM");
+            return false;
+        }
+
+        synchronized (mVpns) {
+            // Tear down existing lockdown if profile was removed
+            mLockdownEnabled = isLockdownVpnEnabled();
+            if (!mLockdownEnabled) {
+                setLockdownTracker(null);
+                return true;
+            }
+
+            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+            if (profileTag == null) {
+                loge("Lockdown VPN configured but cannot be read from keystore");
+                return false;
+            }
+            String profileName = new String(profileTag);
+            final VpnProfile profile = VpnProfile.decode(
+                    profileName, mKeyStore.get(Credentials.VPN + profileName));
+            if (profile == null) {
+                loge("Lockdown VPN configured invalid profile " + profileName);
+                setLockdownTracker(null);
+                return true;
+            }
+            int user = UserHandle.getUserId(mDeps.getCallingUid());
+            Vpn vpn = mVpns.get(user);
+            if (vpn == null) {
+                logw("VPN for user " + user + " not ready yet. Skipping lockdown");
+                return false;
+            }
+            setLockdownTracker(
+                    new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn,  profile));
+        }
+
+        return true;
+    }
+
+    /**
+     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+     */
+    @GuardedBy("mVpns")
+    private void setLockdownTracker(LockdownVpnTracker tracker) {
+        // Shutdown any existing tracker
+        final LockdownVpnTracker existing = mLockdownTracker;
+        // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
+        // necessary onBlockedStatusChanged callbacks.
+        mLockdownTracker = null;
+        if (existing != null) {
+            existing.shutdown();
+        }
+
+        if (tracker != null) {
+            mLockdownTracker = tracker;
+            mLockdownTracker.init();
+        }
+    }
+
+    /**
+     * Throws if there is any currently running, always-on Legacy VPN.
+     *
+     * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
+     * running across the entire system. Tracking for app-based VPNs is done on a per-user,
+     * per-package basis in Vpn.java
+     */
+    @GuardedBy("mVpns")
+    private void throwIfLockdownEnabled() {
+        if (mLockdownEnabled) {
+            throw new IllegalStateException("Unavailable in lockdown mode");
+        }
+    }
+
+    /**
+     * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
+     * some setup and then call {@code establish()} to connect.
+     *
+     * @return {@code true} if the service was started, the service was already connected, or there
+     *         was no always-on VPN to start. {@code false} otherwise.
+     */
+    private boolean startAlwaysOnVpn(int userId) {
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                // Shouldn't happen as all code paths that point here should have checked the Vpn
+                // exists already.
+                Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
+                return false;
+            }
+
+            return vpn.startAlwaysOnVpn(mKeyStore);
+        }
+    }
+
+    @Override
+    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+        enforceSettingsPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
+        }
+    }
+
+    @Override
+    public boolean setAlwaysOnVpnPackage(
+            int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+            if (isLockdownVpnEnabled()) {
+                return false;
+            }
+
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
+                return false;
+            }
+            if (!startAlwaysOnVpn(userId)) {
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String getAlwaysOnVpnPackage(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getAlwaysOnPackage();
+        }
+    }
+
+    @Override
+    public boolean isVpnLockdownEnabled(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            return vpn.getLockdown();
+        }
+    }
+
+    @Override
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getLockdownAllowlist();
+        }
+    }
+
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner() {
+        return getVpnIfOwner(mDeps.getCallingUid());
+    }
+
+    // TODO: stop calling into Vpn.java and get this information from data in this class.
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner(int uid) {
+        final int user = UserHandle.getUserId(uid);
+
+        final Vpn vpn = mVpns.get(user);
+        if (vpn == null) {
+            return null;
+        } else {
+            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
+            return (info == null || info.ownerUid != uid) ? null : vpn;
+        }
+    }
+
+    private void registerReceivers() {
+        // Set up the listener for user state for creating user VPNs.
+        // Should run on mHandler to avoid any races.
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_STARTED);
+        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
+        intentFilter.addAction(Intent.ACTION_USER_ADDED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+
+        mUserAllContext.registerReceiver(
+                mIntentReceiver,
+                intentFilter,
+                null /* broadcastPermission */,
+                mHandler);
+        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
+                mUserPresentReceiver,
+                new IntentFilter(Intent.ACTION_USER_PRESENT),
+                null /* broadcastPermission */,
+                mHandler /* scheduler */);
+
+        // Listen to package add and removal events for all users.
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mUserAllContext.registerReceiver(
+                mIntentReceiver,
+                intentFilter,
+                null /* broadcastPermission */,
+                mHandler);
+
+        // Listen to lockdown VPN reset.
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
+        mUserAllContext.registerReceiver(
+                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+    }
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ensureRunningOnHandlerThread();
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            final Uri packageData = intent.getData();
+            final String packageName =
+                    packageData != null ? packageData.getSchemeSpecificPart() : null;
+
+            if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
+                onVpnLockdownReset();
+            }
+
+            // UserId should be filled for below intents, check the existence.
+            if (userId == UserHandle.USER_NULL) return;
+
+            if (Intent.ACTION_USER_STARTED.equals(action)) {
+                onUserStarted(userId);
+            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+                onUserStopped(userId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                onUserAdded(userId);
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(userId);
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                onUserUnlocked(userId);
+            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                onPackageReplaced(packageName, uid);
+            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                final boolean isReplacing = intent.getBooleanExtra(
+                        Intent.EXTRA_REPLACING, false);
+                onPackageRemoved(packageName, uid, isReplacing);
+            } else {
+                Log.wtf(TAG, "received unexpected intent: " + action);
+            }
+        }
+    };
+
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ensureRunningOnHandlerThread();
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            updateLockdownVpn();
+            // Use the same context that registered receiver before to unregister it. Because use
+            // different context to unregister receiver will cause exception.
+            context.unregisterReceiver(this);
+        }
+    };
+
+    private void onUserStarted(int userId) {
+        synchronized (mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn != null) {
+                loge("Starting user already has a VPN");
+                return;
+            }
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
+            mVpns.put(userId, userVpn);
+            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+                updateLockdownVpn();
+            }
+        }
+    }
+
+    private void onUserStopped(int userId) {
+        synchronized (mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn == null) {
+                loge("Stopped user has no VPN");
+                return;
+            }
+            userVpn.onUserStopped();
+            mVpns.delete(userId);
+        }
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getAlwaysOn();
+        }
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getLockdown();
+        }
+    }
+
+
+    private void onUserAdded(int userId) {
+        synchronized (mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserAdded(userId);
+            }
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        synchronized (mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserRemoved(userId);
+            }
+        }
+    }
+
+    private void onPackageReplaced(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+                log("Restarting always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.startAlwaysOnVpn(mKeyStore);
+            }
+        }
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
+            return;
+        }
+
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+                log("Removing always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+            }
+        }
+    }
+
+    private void onUserUnlocked(int userId) {
+        synchronized (mVpns) {
+            // User present may be sent because of an unlock, which might mean an unlocked keystore.
+            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+                updateLockdownVpn();
+            } else {
+                startAlwaysOnVpn(userId);
+            }
+        }
+    }
+
+    private void onVpnLockdownReset() {
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) mLockdownTracker.reset();
+        }
+    }
+
+
+    @Override
+    public void factoryReset() {
+        enforceSettingsPermission();
+
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)
+                || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
+            return;
+        }
+
+        // Remove always-on package
+        final int userId = UserHandle.getCallingUserId();
+        synchronized (mVpns) {
+            final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
+            if (alwaysOnPackage != null) {
+                setAlwaysOnVpnPackage(userId, null, false, null);
+                setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
+            }
+
+            // Turn Always-on VPN off
+            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                    mLockdownEnabled = false;
+                    setLockdownTracker(null);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            // Turn VPN off
+            VpnConfig vpnConfig = getVpnConfig(userId);
+            if (vpnConfig != null) {
+                if (vpnConfig.legacy) {
+                    prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+                } else {
+                    // Prevent this app (packagename = vpnConfig.user) from initiating
+                    // VPN connections in the future without user intervention.
+                    setVpnPackageAuthorization(
+                            vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
+
+                    prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                }
+            }
+        }
+    }
+
+    private void ensureRunningOnHandlerThread() {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on VpnManagerService thread: "
+                            + Thread.currentThread().getName());
+        }
+    }
+
+    private void enforceControlAlwaysOnVpnPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
+                "VpnManagerService");
+    }
+
+    /**
+     * Require that the caller is either in the same user or has appropriate permission to interact
+     * across users.
+     *
+     * @param userId Target user for whatever operation the current IPC is supposed to perform.
+     */
+    private void enforceCrossUserPermission(int userId) {
+        if (userId == UserHandle.getCallingUserId()) {
+            // Not a cross-user call.
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                "VpnManagerService");
+    }
+
+    private void enforceSettingsPermission() {
+        enforceAnyPermissionOf(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
+    private static void log(String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void logw(String s) {
+        Log.w(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bedd19b..0b1c115 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -186,6 +186,7 @@
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -5626,6 +5627,23 @@
                 ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            final int modeFlags, IBinder callerToken) {
+        final int size = uris.size();
+        int[] res = new int[size];
+        // Default value DENIED.
+        Arrays.fill(res, PackageManager.PERMISSION_DENIED);
+
+        for (int i = 0; i < size; i++) {
+            final Uri uri = uris.get(i);
+            final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+            res[i] = checkUriPermission(ContentProvider.getUriWithoutUserId(uri), pid, uid,
+                    modeFlags, userId, callerToken);
+        }
+        return res;
+    }
+
     /**
      * @param uri This uri must NOT contain an embedded userId.
      * @param userId The userId in which the uri is to be resolved.
@@ -10747,6 +10765,15 @@
                     ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS];
                     ss[INDEX_TOTAL_PSS] += dmabufMapped;
                 }
+
+                // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not
+                // need to be added to kernelUsed.
+                final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb();
+                if (totalDmabufHeapExported >= 0) {
+                    pw.print("DMA-BUF Heaps: ");
+                    pw.println(stringifyKBSize(totalDmabufHeapExported));
+                }
+
                 final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
                 if (totalDmabufHeapPool >= 0) {
                     pw.print("DMA-BUF Heaps pool: ");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index fc7a476..c8630fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1536,6 +1536,14 @@
                 totalPss -= totalMemtrackGraphics;
                 totalPss += dmabufMapped;
             }
+            // These are included in the totalExportedDmabuf above and hence do not need to be added
+            // to kernelUsed.
+            final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb();
+            if (totalExportedDmabufHeap >= 0) {
+                memInfoBuilder.append("DMA-BUF Heap: ");
+                memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap));
+                memInfoBuilder.append("\n");
+            }
 
             final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
             if (totalDmabufHeapPool >= 0) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b994389..52bb55f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -149,7 +149,7 @@
      * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
      * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
      */
-    // TODO: Hook this up (it isn't used yet)
+    // TODO(b/180029015): Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
     private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
@@ -818,14 +818,14 @@
         if (energyConsumerIds.isEmpty()) {
             return null;
         }
-        // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
+        // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
         return getEnergyConsumptionData();
     }
 
     @GuardedBy("mWorkerLock")
     private void addEnergyConsumerIdLocked(
             List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
-        final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this
+        final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
         energyConsumerIds.add(consumerId);
     }
 
@@ -840,7 +840,7 @@
             return null;
         }
 
-        // TODO: Initialize typeToIds
+        // TODO(b/180029015): Initialize typeToIds
         // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
         // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
 
@@ -862,9 +862,9 @@
                 }
             }
             idToConsumer.put(consumer.id, consumer);
-            // TODO: Also populate typeToIds map
+            // TODO(b/180029015): Also populate typeToIds map
         }
-        // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap.
+        // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
         return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0576345..172f707 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2407,6 +2407,11 @@
                         app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
                         false, false,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
+
+                if (Process.createProcessGroup(uid, startResult.pid) < 0) {
+                    Slog.e(ActivityManagerService.TAG, "Unable to create process group for "
+                            + app.processName + " (" + startResult.pid + ")");
+                }
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index e533cc3..47573f3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -607,7 +607,8 @@
     @GuardedBy("mService")
     void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
         synchronized (mProfilerLock) {
-            pw.print(" lastPssTime=");
+            pw.print(prefix);
+            pw.print("lastPssTime=");
             TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
             pw.print(" pssProcState=");
             pw.print(mPssProcState);
@@ -629,9 +630,8 @@
             DebugUtils.printSizeValue(pw, mLastRss * 1024);
             pw.println();
             pw.print(prefix);
-            pw.print(" trimMemoryLevel=");
+            pw.print("trimMemoryLevel=");
             pw.println(mTrimMemoryLevel);
-            pw.println();
             pw.print(prefix); pw.print("procStateMemTracker: ");
             mProcStateMemTracker.dumpLine(pw);
             pw.print(prefix);
@@ -653,5 +653,6 @@
             pw.print(" timeUsed=");
             TimeUtils.formatDuration(mCurCpuTime.get() - lastCpuTime, pw);
         }
+        pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index da8aeb5..42e7ff4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -425,9 +425,10 @@
             pw.print(prefix); pw.print("mInstr="); pw.println(mInstr);
         }
         pw.print(prefix); pw.print("thread="); pw.println(mThread);
-        pw.print(prefix); pw.print("pid="); pw.print(mPid);
+        pw.print(prefix); pw.print("pid="); pw.println(mPid);
         pw.print(prefix); pw.print("lastActivityTime=");
         TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
+        pw.println();
         if (mPersistent || mRemoved) {
             pw.print(prefix); pw.print("persistent="); pw.print(mPersistent);
             pw.print(" removed="); pw.println(mRemoved);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index e1a153d..499fbcb 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1277,7 +1277,7 @@
         pw.print(" set="); pw.println(mSetAdj);
         pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
         pw.print(" setSchedGroup="); pw.print(mSetSchedGroup);
-        pw.print(" systemNoUi="); pw.print(mSystemNoUi);
+        pw.print(" systemNoUi="); pw.println(mSystemNoUi);
         pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
         pw.print(" mRepProcState="); pw.print(mRepProcState);
         pw.print(" setProcState="); pw.print(mSetProcState);
@@ -1297,7 +1297,7 @@
         }
         if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) {
             pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
-            pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean());
+            pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean());
         }
         pw.print(prefix); pw.print("cached="); pw.print(mCached);
         pw.print(" empty="); pw.println(mEmpty);
@@ -1316,12 +1316,12 @@
         }
         if (mHasForegroundActivities || mRepForegroundActivities) {
             pw.print(prefix);
-            pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
+            pw.print("foregroundActivities="); pw.print(mHasForegroundActivities);
             pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")");
         }
         if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) {
             pw.print(prefix);
-            pw.print(" whenUnimportant=");
+            pw.print("whenUnimportant=");
             TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
             pw.println();
         }
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index f37fbf5..f888200 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -798,7 +798,7 @@
             case BiometricAuthenticator.TYPE_FINGERPRINT:
                 return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
             case BiometricAuthenticator.TYPE_FACE:
-                return FaceManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
+                return FaceManager.getAuthHelpMessage(mContext, acquiredInfo, vendorCode);
             default:
                 return null;
         }
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index e3757df..df83df9 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -16,15 +16,24 @@
 
 package com.android.server.compat;
 
+import static android.app.compat.PackageOverride.VALUE_DISABLED;
+import static android.app.compat.PackageOverride.VALUE_ENABLED;
+import static android.app.compat.PackageOverride.VALUE_UNDEFINED;
+
 import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.overrides.ChangeOverrides;
 import com.android.server.compat.overrides.OverrideValue;
+import com.android.server.compat.overrides.RawOverrideValue;
 
 import java.util.HashMap;
 import java.util.List;
@@ -36,7 +45,7 @@
  * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
  * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
  * target SDK criteria set. These settings can be overridden for a specific package using
- * {@link #addPackageOverride(String, boolean)}.
+ * {@link #addPackageOverrideInternal(String, boolean)}.
  *
  * <p>Note, this class is not thread safe so callers must ensure thread safety.
  */
@@ -63,8 +72,8 @@
 
     ChangeListener mListener = null;
 
-    private Map<String, Boolean> mPackageOverrides;
-    private Map<String, Boolean> mDeferredOverrides;
+    private Map<String, Boolean> mEvaluatedOverrides;
+    private Map<String, PackageOverride> mRawOverrides;
 
     public CompatChange(long changeId) {
         this(changeId, null, -1, -1, false, false, null, false);
@@ -113,18 +122,26 @@
      * @param pname Package name to enable the change for.
      * @param enabled Whether or not to enable the change.
      */
-    void addPackageOverride(String pname, boolean enabled) {
+    private void addPackageOverrideInternal(String pname, boolean enabled) {
         if (getLoggingOnly()) {
             throw new IllegalArgumentException(
                     "Can't add overrides for a logging only change " + toString());
         }
-        if (mPackageOverrides == null) {
-            mPackageOverrides = new HashMap<>();
+        if (mEvaluatedOverrides == null) {
+            mEvaluatedOverrides = new HashMap<>();
         }
-        mPackageOverrides.put(pname, enabled);
+        mEvaluatedOverrides.put(pname, enabled);
         notifyListener(pname);
     }
 
+    private void removePackageOverrideInternal(String pname) {
+        if (mEvaluatedOverrides != null) {
+            if (mEvaluatedOverrides.remove(pname) != null) {
+                notifyListener(pname);
+            }
+        }
+    }
+
     /**
      * Tentatively set the state of this change for a given package name.
      * The override will only take effect after that package is installed, if applicable.
@@ -132,17 +149,19 @@
      * <p>Note, this method is not thread safe so callers must ensure thread safety.
      *
      * @param packageName Package name to tentatively enable the change for.
-     * @param enabled Whether or not to enable the change.
+     * @param override The package override to be set
      */
-    void addPackageDeferredOverride(String packageName, boolean enabled) {
+    void addPackageOverride(String packageName, PackageOverride override,
+            OverrideAllowedState allowedState, Context context) {
         if (getLoggingOnly()) {
             throw new IllegalArgumentException(
                     "Can't add overrides for a logging only change " + toString());
         }
-        if (mDeferredOverrides == null) {
-            mDeferredOverrides = new HashMap<>();
+        if (mRawOverrides == null) {
+            mRawOverrides = new HashMap<>();
         }
-        mDeferredOverrides.put(packageName, enabled);
+        mRawOverrides.put(packageName, override);
+        recheckOverride(packageName, allowedState, context);
     }
 
     /**
@@ -157,24 +176,44 @@
      * @return {@code true} if the recheck yielded a result that requires invalidating caches
      *         (a deferred override was consolidated or a regular override was removed).
      */
-    boolean recheckOverride(String packageName, boolean allowed) {
-        // A deferred override now is allowed by the policy, so promote it to a regular override.
-        if (hasDeferredOverride(packageName) && allowed) {
-            boolean overrideValue = mDeferredOverrides.remove(packageName);
-            addPackageOverride(packageName, overrideValue);
-            return true;
+    boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
+            Context context) {
+        boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
+
+        Long version = null;
+        try {
+            ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(
+                    packageName, 0);
+            version = applicationInfo.longVersionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Do nothing
         }
-        // A previously set override is no longer allowed by the policy, so make it deferred.
-        if (hasOverride(packageName) && !allowed) {
-            boolean overrideValue = mPackageOverrides.remove(packageName);
-            addPackageDeferredOverride(packageName, overrideValue);
-            // Notify because the override was removed.
-            notifyListener(packageName);
-            return true;
+
+        // If the app is not installed or no longer has raw overrides, evaluate to false
+        if (version == null || !hasRawOverride(packageName) || !allowed) {
+            removePackageOverrideInternal(packageName);
+            return false;
         }
-        return false;
+
+        // Evaluate the override based on its version
+        int overrideValue = mRawOverrides.get(packageName).evaluate(version);
+        switch (overrideValue) {
+            case VALUE_UNDEFINED:
+                removePackageOverrideInternal(packageName);
+                break;
+            case VALUE_ENABLED:
+                addPackageOverrideInternal(packageName, true);
+                break;
+            case VALUE_DISABLED:
+                addPackageOverrideInternal(packageName, false);
+                break;
+        }
+        return true;
     }
 
+    boolean hasPackageOverride(String pname) {
+        return mRawOverrides != null && mRawOverrides.containsKey(pname);
+    }
     /**
      * Remove any package override for the given package name, restoring the default behaviour.
      *
@@ -182,15 +221,13 @@
      *
      * @param pname Package name to reset to defaults for.
      */
-    void removePackageOverride(String pname) {
-        if (mPackageOverrides != null) {
-            if (mPackageOverrides.remove(pname) != null) {
-                notifyListener(pname);
-            }
+    boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
+            Context context) {
+        if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) {
+            recheckOverride(pname, allowedState, context);
+            return true;
         }
-        if (mDeferredOverrides != null) {
-            mDeferredOverrides.remove(pname);
-        }
+        return false;
     }
 
     /**
@@ -204,8 +241,8 @@
         if (app == null) {
             return defaultValue();
         }
-        if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) {
-            return mPackageOverrides.get(app.packageName);
+        if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) {
+            return mEvaluatedOverrides.get(app.packageName);
         }
         if (getDisabled()) {
             return false;
@@ -223,8 +260,16 @@
      * @return {@code true} if the change should be enabled for the package.
      */
     boolean willBeEnabled(String packageName) {
-        if (hasDeferredOverride(packageName)) {
-            return mDeferredOverrides.get(packageName);
+        if (hasRawOverride(packageName)) {
+            int eval = mRawOverrides.get(packageName).evaluateForAllVersions();
+            switch (eval) {
+                case VALUE_ENABLED:
+                    return true;
+                case VALUE_DISABLED:
+                    return false;
+                case VALUE_UNDEFINED:
+                    return defaultValue();
+            }
         }
         return defaultValue();
     }
@@ -243,8 +288,8 @@
      * @param packageName name of the package
      * @return true if there is such override
      */
-    boolean hasOverride(String packageName) {
-        return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
+    private boolean hasOverride(String packageName) {
+        return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName);
     }
 
     /**
@@ -252,65 +297,77 @@
      * @param packageName name of the package
      * @return true if there is such a deferred override
      */
-    boolean hasDeferredOverride(String packageName) {
-        return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
-    }
-
-    /**
-     * Checks whether a change has any package overrides.
-     * @return true if the change has at least one deferred override
-     */
-    boolean hasAnyPackageOverride() {
-        return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
-    }
-
-    /**
-     * Checks whether a change has any deferred overrides.
-     * @return true if the change has at least one deferred override
-     */
-    boolean hasAnyDeferredOverride() {
-        return mPackageOverrides != null && !mPackageOverrides.isEmpty();
+    private boolean hasRawOverride(String packageName) {
+        return mRawOverrides != null && mRawOverrides.containsKey(packageName);
     }
 
     void loadOverrides(ChangeOverrides changeOverrides) {
-        if (mDeferredOverrides == null) {
-            mDeferredOverrides = new HashMap<>();
+        if (mRawOverrides == null) {
+            mRawOverrides = new HashMap<>();
         }
-        mDeferredOverrides.clear();
-        for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
-            mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
+        mRawOverrides.clear();
+
+        if (mEvaluatedOverrides == null) {
+            mEvaluatedOverrides = new HashMap<>();
+        }
+        mEvaluatedOverrides.clear();
+
+        // Load deferred overrides for backwards compatibility
+        if (changeOverrides.getDeferred() != null) {
+            for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
+                mRawOverrides.put(override.getPackageName(),
+                        new PackageOverride.Builder().setEnabled(
+                                override.getEnabled()).build());
+            }
         }
 
-        if (mPackageOverrides == null) {
-            mPackageOverrides = new HashMap<>();
+        // Load validated overrides. For backwards compatibility, we also add them to raw overrides.
+        if (changeOverrides.getValidated() != null) {
+            for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
+                mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled());
+                mRawOverrides.put(override.getPackageName(),
+                        new PackageOverride.Builder().setEnabled(
+                                override.getEnabled()).build());
+            }
         }
-        mPackageOverrides.clear();
-        for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
-            mPackageOverrides.put(override.getPackageName(), override.getEnabled());
+
+        // Load raw overrides
+        if (changeOverrides.getRaw() != null) {
+            for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) {
+                PackageOverride packageOverride = new PackageOverride.Builder()
+                        .setMinVersionCode(override.getMinVersionCode())
+                        .setMaxVersionCode(override.getMaxVersionCode())
+                        .setEnabled(override.getEnabled())
+                        .build();
+                mRawOverrides.put(override.getPackageName(), packageOverride);
+            }
         }
     }
 
     ChangeOverrides saveOverrides() {
-        if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
+        if (mRawOverrides == null || mRawOverrides.isEmpty()) {
             return null;
         }
         ChangeOverrides changeOverrides = new ChangeOverrides();
         changeOverrides.setChangeId(getId());
-        ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
-        List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
-        if (mDeferredOverrides != null) {
-            for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
-                OverrideValue override = new OverrideValue();
+        ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw();
+        List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue();
+        if (mRawOverrides != null) {
+            for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) {
+                RawOverrideValue override = new RawOverrideValue();
                 override.setPackageName(entry.getKey());
-                override.setEnabled(entry.getValue());
-                deferredList.add(override);
+                override.setMinVersionCode(entry.getValue().getMinVersionCode());
+                override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
+                override.setEnabled(entry.getValue().getEnabled());
+                rawList.add(override);
             }
         }
-        changeOverrides.setDeferred(deferredOverrides);
+        changeOverrides.setRaw(rawOverrides);
+
         ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
         List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
-        if (mPackageOverrides != null) {
-            for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
+        if (mEvaluatedOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) {
                 OverrideValue override = new OverrideValue();
                 override.setPackageName(entry.getKey());
                 override.setEnabled(entry.getValue());
@@ -337,11 +394,11 @@
         if (getLoggingOnly()) {
             sb.append("; loggingOnly");
         }
-        if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
-            sb.append("; packageOverrides=").append(mPackageOverrides);
+        if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) {
+            sb.append("; packageOverrides=").append(mEvaluatedOverrides);
         }
-        if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
-            sb.append("; deferredOverrides=").append(mDeferredOverrides);
+        if (mRawOverrides != null && mRawOverrides.size() > 0) {
+            sb.append("; rawOverrides=").append(mRawOverrides);
         }
         if (getOverridable()) {
             sb.append("; overridable");
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 6b77b9d..422991e 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,6 +17,7 @@
 package com.android.server.compat;
 
 import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -31,6 +32,7 @@
 import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
@@ -70,11 +72,13 @@
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
     private final OverrideValidatorImpl mOverrideValidator;
+    private Context mContext;
     private File mOverridesFile;
 
     @VisibleForTesting
     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
         mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
+        mContext = context;
     }
 
     static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -210,17 +214,33 @@
      * @throws IllegalStateException if overriding is not allowed
      */
     boolean addOverride(long changeId, String packageName, boolean enabled) {
-        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
+        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
+                new PackageOverride.Builder().setEnabled(enabled).build());
         saveOverrides();
         invalidateCache();
         return alreadyKnown;
     }
 
     /**
-     * Unsafe version of {@link #addOverride(long, String, boolean)}.
-     * It does not invalidate the cache nor save the overrides.
+     * Overrides the enabled state for a given change and app.
+     *
+     * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+     *
+     * @param overrides   list of overrides to default changes config.
+     * @param packageName app for which the overrides will be applied.
      */
-    private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
+    void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
+        synchronized (mChanges) {
+            for (Long changeId : overrides.overrides.keySet()) {
+                addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
+            }
+            saveOverrides();
+            invalidateCache();
+        }
+    }
+
+    private boolean addOverrideUnsafe(long changeId, String packageName,
+            PackageOverride overrides) {
         boolean alreadyKnown = true;
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -232,17 +252,8 @@
                 c = new CompatChange(changeId);
                 addChange(c);
             }
-            switch (allowedState.state) {
-                case OverrideAllowedState.ALLOWED:
-                    c.addPackageOverride(packageName, enabled);
-                    break;
-                case OverrideAllowedState.DEFERRED_VERIFICATION:
-                    c.addPackageDeferredOverride(packageName, enabled);
-                    break;
-                default:
-                    throw new IllegalStateException("Should only be able to override changes that "
-                            + "are allowed or can be deferred.");
-            }
+            c.addPackageOverride(packageName, overrides, allowedState, mContext);
+            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -311,47 +322,20 @@
      * It does not invalidate the cache nor save the overrides.
      */
     private boolean removeOverrideUnsafe(long changeId, String packageName) {
-        boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
             if (c != null) {
-                // Always allow removing a deferred override.
-                if (c.hasDeferredOverride(packageName)) {
-                    c.removePackageOverride(packageName);
-                    overrideExists = true;
-                } else if (c.hasOverride(packageName)) {
-                    // Regular overrides need to pass the policy.
-                    overrideExists = true;
-                    OverrideAllowedState allowedState =
-                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                OverrideAllowedState allowedState =
+                        mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                if (c.hasPackageOverride(packageName)) {
                     allowedState.enforce(changeId, packageName);
-                    c.removePackageOverride(packageName);
+                    c.removePackageOverride(packageName, allowedState, mContext);
+                    invalidateCache();
+                    return true;
                 }
             }
         }
-        return overrideExists;
-    }
-
-    /**
-     * Overrides the enabled state for a given change and app.
-     *
-     * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
-     *
-     * @param overrides   list of overrides to default changes config
-     * @param packageName app for which the overrides will be applied
-     */
-    void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
-        synchronized (mChanges) {
-            for (Long changeId : overrides.enabledChanges()) {
-                addOverrideUnsafe(changeId, packageName, true);
-            }
-            for (Long changeId : overrides.disabledChanges()) {
-                addOverrideUnsafe(changeId, packageName, false);
-
-            }
-            saveOverrides();
-            invalidateCache();
-        }
+        return false;
     }
 
     /**
@@ -402,7 +386,8 @@
     int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverrideUnsafe(changeId, packageName, true);
+            addOverrideUnsafe(changeId, packageName,
+                    new PackageOverride.Builder().setEnabled(true).build());
         }
         saveOverrides();
         invalidateCache();
@@ -418,7 +403,8 @@
     int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverrideUnsafe(changeId, packageName, false);
+            addOverrideUnsafe(changeId, packageName,
+                    new PackageOverride.Builder().setEnabled(false).build());
         }
         saveOverrides();
         invalidateCache();
@@ -615,8 +601,7 @@
                 CompatChange c = mChanges.valueAt(idx);
                 OverrideAllowedState allowedState =
                         mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
-                boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED);
-                shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride);
+                shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext);
             }
             if (shouldInvalidateCache) {
                 invalidateCache();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 51ba5f7..d17753f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -25,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.app.compat.PackageOverride;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +44,7 @@
 import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
@@ -51,6 +53,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * System server internal API for gating and reporting compatibility changes.
@@ -161,6 +165,22 @@
     @Override
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
         checkCompatChangeOverridePermission();
+        Map<Long, PackageOverride> overridesMap = new HashMap<>();
+        for (long change : overrides.enabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+        }
+        for (long change : overrides.disabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+                    .build());
+        }
+        mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
+        killPackage(packageName);
+    }
+
+    @Override
+    public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides,
+            String packageName) {
+        checkCompatChangeOverridePermission();
         mCompatConfig.addOverrides(overrides, packageName);
         killPackage(packageName);
     }
@@ -168,7 +188,15 @@
     @Override
     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
         checkCompatChangeOverridePermission();
-        mCompatConfig.addOverrides(overrides, packageName);
+        Map<Long, PackageOverride> overridesMap = new HashMap<>();
+        for (long change : overrides.enabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+        }
+        for (long change : overrides.disabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+                    .build());
+        }
+        mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 397af7b..61b11a5 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,7 +16,6 @@
 
 package com.android.server.connectivity;
 
-import static android.net.NetworkCapabilities.MAX_TRANSPORT;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -25,15 +24,14 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
-import android.net.ConnectivityManager;
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
 import android.net.metrics.DnsEvent;
-import android.net.metrics.ConnectStats;
 import android.net.metrics.IpManagerEvent;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.metrics.NetworkEvent;
@@ -41,12 +39,13 @@
 import android.net.metrics.ValidationProbeEvent;
 import android.net.metrics.WakeupStats;
 import android.os.Parcelable;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
+
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -361,29 +360,22 @@
                 return IpConnectivityLogClass.UNKNOWN;
             case 1:
                 int t = Long.numberOfTrailingZeros(transports);
-                return transportToLinkLayer(t);
+                return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN);
             default:
                 return IpConnectivityLogClass.MULTIPLE;
         }
     }
 
-    private static int transportToLinkLayer(int transport) {
-        if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
-            return TRANSPORT_LINKLAYER_MAP[transport];
-        }
-        return IpConnectivityLogClass.UNKNOWN;
-    }
-
-    private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+    private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray();
     static {
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR]   = IpConnectivityLogClass.CELLULAR;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI]       = IpConnectivityLogClass.WIFI;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH]  = IpConnectivityLogClass.BLUETOOTH;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET]   = IpConnectivityLogClass.ETHERNET;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN]        = IpConnectivityLogClass.UNKNOWN;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN]     = IpConnectivityLogClass.LOWPAN;
-    };
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN);
+    }
 
     private static int ifnameToLinkLayer(String ifname) {
         // Do not try to catch all interface names with regexes, instead only catch patterns that
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 6f112d7..508739f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -79,7 +79,6 @@
     // server.
     public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS";
     public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS";
-    public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
 
     // The context is for the current user (system server)
     private final Context mContext;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 33c19b1..a769e88 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,10 +21,10 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -172,6 +172,12 @@
      */
     @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB
 
+    /**
+     * Network score that VPNs will announce to ConnectivityService.
+     * TODO: remove when the network scoring refactor lands.
+     */
+    private static final int VPN_DEFAULT_SCORE = 101;
+
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
 
@@ -496,6 +502,11 @@
         updateAlwaysOnNotification(detailedState);
     }
 
+    private void resetNetworkCapabilities() {
+        mNetworkCapabilities.setUids(null);
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
+    }
+
     /**
      * Chooses whether to force all connections to go though VPN.
      *
@@ -520,6 +531,11 @@
         }
     }
 
+    /** Returns the package name that is currently prepared. */
+    public String getPackage() {
+        return mPackage;
+    }
+
     /**
      * Check whether to prevent all traffic outside of a VPN even when the VPN is not connected.
      *
@@ -930,8 +946,7 @@
                 agentDisconnect();
                 jniReset(mInterface);
                 mInterface = null;
-                mNetworkCapabilities.setUids(null);
-                mNetworkCapabilities.setTransportInfo(null);
+                resetNetworkCapabilities();
             }
 
             // Revoke the connection or stop the VpnRunner.
@@ -1229,8 +1244,7 @@
         }
 
         mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
-                mNetworkCapabilities, lp,
-                ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
+                mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
             @Override
             public void unwanted() {
                 // We are user controlled, not driven by NetworkRequest.
@@ -1744,8 +1758,7 @@
 
     private void cleanupVpnStateLocked() {
         mStatusIntent = null;
-        mNetworkCapabilities.setUids(null);
-        mNetworkCapabilities.setTransportInfo(null);
+        resetNetworkCapabilities();
         mConfig = null;
         mInterface = null;
 
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 1b27572..09c0937 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -72,11 +72,11 @@
     }
 
     @Override
-    public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+    public int updateFontFile(@NonNull FontUpdateRequest request, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
         Objects.requireNonNull(request);
         Objects.requireNonNull(request.getFd());
         Objects.requireNonNull(request.getSignature());
-        Preconditions.checkArgumentNonnegative(baseVersion);
         getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
                 "UPDATE_FONTS permission required.");
         try {
@@ -88,6 +88,21 @@
         }
     }
 
+    @Override
+    public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
+        Objects.requireNonNull(requests);
+        getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
+                "UPDATE_FONTS permission required.");
+        try {
+            update(baseVersion, requests);
+            return FontManager.RESULT_SUCCESS;
+        } catch (SystemFontException e) {
+            Slog.e(TAG, "Failed to update font family", e);
+            return e.getErrorCode();
+        }
+    }
+
     /* package */ static class SystemFontException extends AndroidException {
         private final int mErrorCode;
 
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 45f2a38..08ddc6d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -45,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Manages set of updatable font files.
@@ -182,8 +183,12 @@
             List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
             for (int i = 0; i < fontFamilies.size(); i++) {
                 FontConfig.FontFamily fontFamily = fontFamilies.get(i);
-                // Ignore failures as updated fonts may be obsoleted by system OTA update.
-                addFontFamily(fontFamily);
+                try {
+                    addFontFamily(fontFamily);
+                } catch (SystemFontException e) {
+                    // Ignore failures as updated fonts may be obsoleted by system OTA update.
+                    Slog.i(TAG, "Obsolete font family: " + fontFamily.getName());
+                }
             }
             success = true;
         } catch (Throwable t) {
@@ -236,10 +241,7 @@
                                 request.getFd().getFileDescriptor(), request.getSignature());
                         break;
                     case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
-                        // TODO: define error code.
-                        if (!addFontFamily(request.getFontFamily())) {
-                            throw new IllegalArgumentException("Invalid font family");
-                        }
+                        addFontFamily(request.getFontFamily());
                         break;
                 }
             }
@@ -495,18 +497,15 @@
      * Unnamed font families are used as other named font family's fallback fonts to guarantee a
      * complete glyph coverage.
      */
-    private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
-        if (fontFamily.getName() == null) {
-            Slog.e(TAG, "Name is null.");
-            return false;
-        }
+    private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException {
+        Objects.requireNonNull(fontFamily.getName());
         FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
         if (resolvedFontFamily == null) {
-            Slog.e(TAG, "Required fonts are not available");
-            return false;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_NOT_FOUND,
+                    "Required fonts are not available");
         }
         mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
-        return true;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index cb47bb2..86a8e36 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -103,10 +103,11 @@
         if (mIsCec20) {
             sendSetStreamPath();
         }
-        if (!mIsCec20 || mTarget.getDevicePowerStatus()
-                              == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+        int targetPowerStatus = localDevice().mService.getHdmiCecNetwork()
+                .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus();
+        if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
             queryDevicePowerStatus();
-        } else if (mTarget.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON) {
+        } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
             invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS);
             return true;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 6918064..f64efe7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -411,6 +411,12 @@
             case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
                 break;
+            case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY);
+                break;
+            case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
+                break;
         }
     }
 
@@ -447,6 +453,8 @@
                 Global.HDMI_CEC_VERSION,
                 Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
+                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
         };
         for (String setting: settings) {
             resolver.registerContentObserver(Global.getUriFor(setting), false,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 382f0f9..d8914b3 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -654,7 +654,9 @@
                     FOLLOWER_SAFETY_TIMEOUT);
             return true;
         }
-        return false;
+
+        mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+        return true;
     }
 
     @ServiceThreadOnly
@@ -666,9 +668,8 @@
             final long upTime = SystemClock.uptimeMillis();
             injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
             mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
-            return true;
         }
-        return false;
+        return true;
     }
 
     static void injectKeyEvent(long time, int action, int keycode, int repeat) {
@@ -788,10 +789,7 @@
     }
 
     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
-        // The default behavior of <Record TV Screen> is replying <Feature Abort> with
-        // "Cannot provide source".
-        mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
-        return true;
+        return false;
     }
 
     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index a3e18d1..8d6bcad 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1079,7 +1079,10 @@
                         message.getSource(),
                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
             }
-            return super.handleRecordTvScreen(message);
+            // The default behavior of <Record TV Screen> is replying <Feature Abort> with
+            // "Cannot provide source".
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
+            return true;
         }
 
         int recorderAddress = message.getSource();
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 47cb43e..5eec315 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -78,6 +78,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
+import android.provider.Settings;
 import android.stats.location.LocationStatsEnums;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
@@ -97,6 +98,8 @@
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.AppOpsHelper;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
 import com.android.server.location.injector.EmergencyHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationAttributionHelper;
@@ -108,6 +111,8 @@
 import com.android.server.location.injector.SystemAlarmHelper;
 import com.android.server.location.injector.SystemAppForegroundHelper;
 import com.android.server.location.injector.SystemAppOpsHelper;
+import com.android.server.location.injector.SystemDeviceIdleHelper;
+import com.android.server.location.injector.SystemDeviceStationaryHelper;
 import com.android.server.location.injector.SystemEmergencyHelper;
 import com.android.server.location.injector.SystemLocationPermissionsHelper;
 import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
@@ -120,6 +125,7 @@
 import com.android.server.location.provider.MockLocationProvider;
 import com.android.server.location.provider.PassiveLocationProvider;
 import com.android.server.location.provider.PassiveLocationProviderManager;
+import com.android.server.location.provider.StationaryThrottlingLocationProvider;
 import com.android.server.location.provider.proxy.ProxyLocationProvider;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 
@@ -313,6 +319,18 @@
 
             manager.startManager();
             if (realProvider != null) {
+
+                // custom logic wrapping all non-passive providers
+                if (manager != mPassiveManager) {
+                    boolean enableStationaryThrottling = Settings.Global.getInt(
+                            mContext.getContentResolver(),
+                            Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
+                    if (enableStationaryThrottling) {
+                        realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
+                                mInjector, realProvider, mEventLog);
+                    }
+                }
+
                 manager.setRealProvider(realProvider);
             }
             mProviderManagers.add(manager);
@@ -1368,6 +1386,8 @@
         private final SystemAppForegroundHelper mAppForegroundHelper;
         private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
         private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
+        private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
+        private final SystemDeviceIdleHelper mDeviceIdleHelper;
         private final LocationAttributionHelper mLocationAttributionHelper;
         private final LocationUsageLogger mLocationUsageLogger;
 
@@ -1391,6 +1411,8 @@
             mAppForegroundHelper = new SystemAppForegroundHelper(context);
             mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
+            mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
+            mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
             mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
             mLocationUsageLogger = new LocationUsageLogger();
         }
@@ -1451,6 +1473,16 @@
         }
 
         @Override
+        public DeviceStationaryHelper getDeviceStationaryHelper() {
+            return mDeviceStationaryHelper;
+        }
+
+        @Override
+        public DeviceIdleHelper getDeviceIdleHelper() {
+            return mDeviceIdleHelper;
+        }
+
+        @Override
         public LocationAttributionHelper getLocationAttributionHelper() {
             return mLocationAttributionHelper;
         }
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 865d41f..dbfd0a5 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -60,7 +60,8 @@
     private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
     private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
     private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
-    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
+    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
+    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
@@ -167,6 +168,11 @@
         getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
     }
 
+    /** Logs that a provider has entered or exited stationary throttling. */
+    public void logProviderStationaryThrottled(String provider, boolean throttled) {
+        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+    }
+
     /** Logs that the location power save mode has changed. */
     public void logLocationPowerSaveMode(
             @LocationPowerSaveMode int locationPowerSaveMode) {
@@ -198,6 +204,9 @@
             case EVENT_PROVIDER_DELIVER_LOCATION:
                 return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
                         (Integer) args[1], (CallerIdentity) args[2]);
+            case EVENT_PROVIDER_STATIONARY_THROTTLED:
+                return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
+                        (Boolean) args[1]);
             case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
                 return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
             default:
@@ -332,6 +341,23 @@
         }
     }
 
+    private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
+
+        private final boolean mStationaryThrottled;
+
+        private ProviderStationaryThrottledEvent(long timeDelta, String provider,
+                boolean stationaryThrottled) {
+            super(timeDelta, provider);
+            mStationaryThrottled = stationaryThrottled;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
+                    : "unthrottled");
+        }
+    }
+
     private static final class LocationPowerSaveModeEvent extends LogEvent {
 
         @LocationPowerSaveMode
diff --git a/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
new file mode 100644
index 0000000..e820b00
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.location.injector;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for device idle status.
+ */
+public abstract class DeviceIdleHelper {
+
+    /**
+     * Listener for device stationary status.
+     */
+    public interface DeviceIdleListener {
+        /**
+         * Called when device idle state has changed.
+         */
+        void onDeviceIdleChanged(boolean deviceIdle);
+    }
+
+    private final CopyOnWriteArrayList<DeviceIdleListener> mListeners;
+
+    protected DeviceIdleHelper() {
+        mListeners = new CopyOnWriteArrayList<>();
+    }
+
+    /**
+     * Adds a listener for device idle status.
+     */
+    public final synchronized void addListener(DeviceIdleListener listener) {
+        if (mListeners.add(listener) && mListeners.size() == 1) {
+            registerInternal();
+        }
+    }
+
+    /**
+     * Removes a listener for device idle status.
+     */
+    public final synchronized void removeListener(DeviceIdleListener listener) {
+        if (mListeners.remove(listener) && mListeners.isEmpty()) {
+            unregisterInternal();
+        }
+    }
+
+    protected final void notifyDeviceIdleChanged() {
+        boolean deviceIdle = isDeviceIdle();
+
+        for (DeviceIdleListener listener : mListeners) {
+            listener.onDeviceIdleChanged(deviceIdle);
+        }
+    }
+
+    protected abstract void registerInternal();
+
+    protected abstract void unregisterInternal();
+
+    /**
+     * Returns true if the device is currently idle.
+     */
+    public abstract boolean isDeviceIdle();
+}
diff --git a/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
new file mode 100644
index 0000000..b77c0f7
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+/**
+ * Provides accessors and listeners for device stationary status.
+ */
+public abstract class DeviceStationaryHelper {
+
+    /**
+     * Adds a listener for stationary status. The listener will be immediately invoked with the
+     * current stationary status.
+     */
+    public abstract void addListener(DeviceIdleInternal.StationaryListener listener);
+
+    /**
+     * Removes a listener for stationary status.
+     */
+    public abstract void removeListener(DeviceIdleInternal.StationaryListener listener);
+}
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 0e157c2..b035118 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -48,6 +48,12 @@
     /** Returns a ScreenInteractiveHelper. */
     ScreenInteractiveHelper getScreenInteractiveHelper();
 
+    /** Returns a DeviceStationaryHelper. */
+    DeviceStationaryHelper getDeviceStationaryHelper();
+
+    /** Returns a DeviceIdleHelper. */
+    DeviceIdleHelper getDeviceIdleHelper();
+
     /** Returns a LocationAttributionHelper. */
     LocationAttributionHelper getLocationAttributionHelper();
 
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
new file mode 100644
index 0000000..6a89079
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceIdleHelper extends DeviceIdleHelper {
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+
+    private @Nullable BroadcastReceiver mReceiver;
+
+    public SystemDeviceIdleHelper(Context context) {
+        mContext = context;
+        mPowerManager = context.getSystemService(PowerManager.class);
+    }
+
+    @Override
+    protected void registerInternal() {
+        if (mReceiver == null) {
+            mReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    notifyDeviceIdleChanged();
+                }
+            };
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                    FgThread.getHandler());
+        }
+    }
+
+    @Override
+    protected void unregisterInternal() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    @Override
+    public boolean isDeviceIdle() {
+        return mPowerManager.isDeviceIdleMode();
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
new file mode 100644
index 0000000..6f0e681
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+
+import java.util.Objects;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
+
+    private final DeviceIdleInternal mDeviceIdle;
+
+    public SystemDeviceStationaryHelper() {
+        mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
+    }
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.registerStationaryListener(listener);
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.unregisterStationaryListener(listener);
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index e22a014..4e0a0b8 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -337,8 +337,10 @@
 
         @Override
         public State setListener(@Nullable Listener listener) {
-            return mInternalState.updateAndGet(
-                    internalState -> internalState.withListener(listener)).state;
+            InternalState oldInternalState = mInternalState.getAndUpdate(
+                    internalState -> internalState.withListener(listener));
+            Preconditions.checkState(listener == null || oldInternalState.listener == null);
+            return oldInternalState.state;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
new file mode 100644
index 0000000..a3ec867
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.Bundle;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class for wrapping location providers. Subclasses MUST ensure that
+ * {@link #initializeDelegate()} is invoked before the delegate is used.
+ */
+class DelegateLocationProvider extends AbstractLocationProvider
+        implements AbstractLocationProvider.Listener {
+
+    private final Object mInitializationLock = new Object();
+
+    protected final AbstractLocationProvider mDelegate;
+
+    private boolean mInitialized = false;
+
+    DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) {
+        super(executor, null, null);
+
+        mDelegate = delegate;
+    }
+
+    /**
+     * This function initializes state from the delegate and allows all other callbacks to be
+     * immediately invoked. If this method is invoked from a subclass constructor, it should be the
+     * last statement in the constructor since it allows the subclass' reference to escape.
+     */
+    protected void initializeDelegate() {
+        synchronized (mInitializationLock) {
+            Preconditions.checkState(!mInitialized);
+            setState(previousState -> mDelegate.getController().setListener(this));
+            mInitialized = true;
+        }
+    }
+
+    // must be invoked in every listener callback to ensure they don't run until initialized
+    protected final void waitForInitialization() {
+        // callbacks can start coming as soon as the setListener() call in initializeDelegate
+        // completes - but we can't allow any to proceed until the setState call afterwards
+        // completes. acquiring the initialization lock here blocks until initialization is
+        // complete, and we verify this wasn't called before initializeDelegate for some reason.
+        synchronized (mInitializationLock) {
+            Preconditions.checkState(mInitialized);
+        }
+    }
+
+    @Override
+    public void onStateChanged(State oldState, State newState) {
+        waitForInitialization();
+        setState(previousState -> newState);
+    }
+
+    @Override
+    public void onReportLocation(LocationResult locationResult) {
+        waitForInitialization();
+        reportLocation(locationResult);
+    }
+
+    @Override
+    protected void onStart() {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().start();
+    }
+
+    @Override
+    protected void onStop() {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().stop();
+    }
+
+    @Override
+    protected void onSetRequest(ProviderRequest request) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().setRequest(request);
+    }
+
+    @Override
+    protected void onFlush(Runnable callback) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().flush(callback);
+    }
+
+    @Override
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().sendExtraCommand(uid, pid, command, extras);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.dump(fd, pw, args);
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
new file mode 100644
index 0000000..6f4aa64
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import static android.location.provider.ProviderRequest.INTERVAL_DISABLED;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.FgThread;
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
+import com.android.server.location.injector.Injector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Throttles location providers completely while the device is in doze and stationary, and returns
+ * the last known location as a new location at the appropriate interval instead. Hypothetically,
+ * this throttling could be applied only when the device is stationary - however we don't trust the
+ * accuracy of the on-device SMD (which could allow for 100s of feet of movement without triggering
+ * in some use cases) enough to rely just on this. Instead we require the device to be in doze mode
+ * and stationary to narrow down the effect of false positives/negatives.
+ */
+public final class StationaryThrottlingLocationProvider extends DelegateLocationProvider
+        implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
+
+    private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+
+    private final Object mLock = new Object();
+
+    private final String mName;
+    private final DeviceIdleHelper mDeviceIdleHelper;
+    private final DeviceStationaryHelper mDeviceStationaryHelper;
+    private final LocationEventLog mEventLog;
+
+    @GuardedBy("mLock")
+    private boolean mDeviceIdle = false;
+    @GuardedBy("mLock")
+    private boolean mDeviceStationary = false;
+    @GuardedBy("mLock")
+    private long mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+    @GuardedBy("mLock")
+    private ProviderRequest mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+    @GuardedBy("mLock")
+    private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+    @GuardedBy("mLock")
+    private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+    @GuardedBy("mLock")
+    private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
+
+    @GuardedBy("mLock")
+    private @Nullable Location mLastLocation;
+
+    public StationaryThrottlingLocationProvider(String name, Injector injector,
+            AbstractLocationProvider delegate, LocationEventLog eventLog) {
+        super(DIRECT_EXECUTOR, delegate);
+
+        mName = name;
+        mDeviceIdleHelper = injector.getDeviceIdleHelper();
+        mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
+        mEventLog = eventLog;
+
+        // must be last statement in the constructor because reference is escaping
+        initializeDelegate();
+    }
+
+    @Override
+    public void onReportLocation(LocationResult locationResult) {
+        super.onReportLocation(locationResult);
+
+        synchronized (mLock) {
+            mLastLocation = locationResult.getLastLocation();
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        mDelegate.getController().start();
+
+        synchronized (mLock) {
+            mDeviceIdleHelper.addListener(this);
+            mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
+            mDeviceStationaryHelper.addListener(this);
+            mDeviceStationary = false;
+            mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        synchronized (mLock) {
+            mDeviceStationaryHelper.removeListener(this);
+            mDeviceIdleHelper.removeListener(this);
+
+            mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+            mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+            mThrottlingIntervalMs = INTERVAL_DISABLED;
+
+            if (mDeliverLastLocationCallback != null) {
+                FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+                mDeliverLastLocationCallback = null;
+            }
+
+            mLastLocation = null;
+        }
+
+        mDelegate.getController().stop();
+    }
+
+    @Override
+    protected void onSetRequest(ProviderRequest request) {
+        synchronized (mLock) {
+            mIncomingRequest = request;
+            onThrottlingChangedLocked(true);
+        }
+    }
+
+    @Override
+    public void onDeviceIdleChanged(boolean deviceIdle) {
+        synchronized (mLock) {
+            if (deviceIdle == mDeviceIdle) {
+                return;
+            }
+
+            mDeviceIdle = deviceIdle;
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    public void onDeviceStationaryChanged(boolean deviceStationary) {
+        synchronized (mLock) {
+            if (mDeviceStationary == deviceStationary) {
+                return;
+            }
+
+            mDeviceStationary = deviceStationary;
+            if (mDeviceStationary) {
+                mDeviceStationaryRealtimeMs = SystemClock.elapsedRealtime();
+            } else {
+                mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+            }
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onThrottlingChangedLocked(boolean deliverImmediate) {
+        long throttlingIntervalMs = INTERVAL_DISABLED;
+        if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+                && mLastLocation != null
+                && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
+                <= MAX_STATIONARY_LOCATION_AGE_MS) {
+            throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+        }
+
+        ProviderRequest newRequest;
+        if (throttlingIntervalMs != INTERVAL_DISABLED) {
+            newRequest = ProviderRequest.EMPTY_REQUEST;
+        } else {
+            newRequest = mIncomingRequest;
+        }
+
+        if (!newRequest.equals(mOutgoingRequest)) {
+            mOutgoingRequest = newRequest;
+            mDelegate.getController().setRequest(mOutgoingRequest);
+        }
+
+        if (throttlingIntervalMs == mThrottlingIntervalMs) {
+            return;
+        }
+
+        long oldThrottlingIntervalMs = mThrottlingIntervalMs;
+        mThrottlingIntervalMs = throttlingIntervalMs;
+
+        if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+            if (oldThrottlingIntervalMs == INTERVAL_DISABLED) {
+                if (D) {
+                    Log.d(TAG, mName + " provider stationary throttled");
+                }
+                mEventLog.logProviderStationaryThrottled(mName, true);
+            }
+
+            if (mDeliverLastLocationCallback != null) {
+                FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+            }
+            mDeliverLastLocationCallback = new DeliverLastLocationRunnable();
+
+            Preconditions.checkState(mLastLocation != null);
+
+            if (deliverImmediate) {
+                FgThread.getHandler().post(mDeliverLastLocationCallback);
+            } else {
+                long delayMs = mThrottlingIntervalMs - mLastLocation.getElapsedRealtimeAgeMillis();
+                FgThread.getHandler().postDelayed(mDeliverLastLocationCallback, delayMs);
+            }
+        } else {
+            if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
+                mEventLog.logProviderStationaryThrottled(mName, false);
+                if (D) {
+                    Log.d(TAG, mName + " provider stationary unthrottled");
+                }
+            }
+
+            FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+            mDeliverLastLocationCallback = null;
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+            pw.println("stationary throttled=" + mLastLocation);
+        } else {
+            pw.print("stationary throttled=false");
+            if (!mDeviceIdle) {
+                pw.print(" (not idle)");
+            }
+            if (!mDeviceStationary) {
+                pw.print(" (not stationary)");
+            }
+            pw.println();
+        }
+
+        mDelegate.dump(fd, pw, args);
+    }
+
+    private class DeliverLastLocationRunnable implements Runnable {
+        @Override
+        public void run() {
+            Location location;
+            synchronized (mLock) {
+                if (mDeliverLastLocationCallback != this) {
+                    return;
+                }
+                if (mLastLocation == null) {
+                    return;
+                }
+
+                location = new Location(mLastLocation);
+                location.setTime(System.currentTimeMillis());
+                location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+                if (location.hasSpeed()) {
+                    location.removeSpeed();
+                    if (location.hasSpeedAccuracy()) {
+                        location.removeSpeedAccuracy();
+                    }
+                }
+                if (location.hasBearing()) {
+                    location.removeBearing();
+                    if (location.hasBearingAccuracy()) {
+                        location.removeBearingAccuracy();
+                    }
+                }
+
+                mLastLocation = location;
+                FgThread.getHandler().postDelayed(this, mThrottlingIntervalMs);
+            }
+
+            reportLocation(LocationResult.wrap(location));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 32d637f..4c97f64 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request) {
+    protected void onSetRequest(ProviderRequest request) {
         mRequest = request;
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 67fae05..6a5c2d89 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -187,6 +187,7 @@
             pw.println("");
             pw.println("  remove-cache [--user USER_ID]");
             pw.println("    Removes cached unified challenge for the managed profile.");
+            pw.println("");
             pw.println("  set-resume-on-reboot-provider-package <package_name>");
             pw.println("    Sets the package name for server based resume on reboot service "
                     + "provider.");
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
index 38eeb88..af0774c 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -35,6 +35,12 @@
      */
     private static final int CURRENT_VERSION = 2;
 
+    /**
+    * This is the legacy version of the escrow data format for R builds. The escrow data is only
+    * encrypted by the escrow key, without additional wrap of another key from keystore.
+    */
+    private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1;
+
     private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
             RebootEscrowKey key) {
         mSpVersion = spVersion;
@@ -64,6 +70,19 @@
         return mKey;
     }
 
+    private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks,
+            DataInputStream dis) throws IOException {
+        if (kk == null) {
+            throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the"
+                    + " escrow data");
+        }
+
+        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
+        // escrow key.
+        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
+        return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
+    }
+
     static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
             throws IOException {
         Objects.requireNonNull(ks);
@@ -71,17 +90,20 @@
 
         DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
         int version = dis.readInt();
-        if (version != CURRENT_VERSION) {
-            throw new IOException("Unsupported version " + version);
-        }
         byte spVersion = dis.readByte();
-
-        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
-        // escrow key.
-        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
-        final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
-
-        return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+        switch (version) {
+            case CURRENT_VERSION: {
+                byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis);
+                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+            }
+            case LEGACY_SINGLE_ENCRYPTED_VERSION: {
+                // Decrypt the blob with the escrow key directly.
+                byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis);
+                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+            }
+            default:
+                throw new IOException("Unsupported version " + version);
+        }
     }
 
     static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 53b62ca..30ea555 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -146,6 +146,7 @@
             RebootEscrowProviderInterface rebootEscrowProvider;
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                     "server_based_ror_enabled", false)) {
+                Slog.i(TAG, "Using server based resume on reboot");
                 rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
             } else {
                 rebootEscrowProvider = new RebootEscrowProviderHalImpl();
@@ -272,6 +273,10 @@
         // generated before reboot. Note that we will clear the escrow key even if the keystore key
         // is null.
         SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
+        if (kk == null) {
+            Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
+        }
+
         RebootEscrowKey escrowKey;
         try {
             escrowKey = getAndClearRebootEscrowKey(kk);
@@ -281,7 +286,7 @@
             return;
         }
 
-        if (kk == null || escrowKey == null) {
+        if (escrowKey == null) {
             onGetRebootEscrowKeyFailed(users);
             return;
         }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index 9d09637..b3b4546 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -136,6 +136,11 @@
             Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
             return null;
         }
+        if (decryptionKey == null) {
+            Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is"
+                    + " null.");
+            return null;
+        }
 
         Slog.i(TAG, "Loaded reboot escrow server blob from storage");
         try {
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 6d1c680..3cc32be 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -16,10 +16,10 @@
 
 package com.android.server.net;
 
-import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 import static android.provider.Settings.ACTION_VPN_SETTINGS;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,43 +28,37 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
 import android.os.Handler;
 import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
-import com.android.server.ConnectivityService;
-import com.android.server.EventLogTags;
 import com.android.server.connectivity.Vpn;
 
 import java.util.List;
 import java.util.Objects;
 
 /**
- * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
- * connected and kicks off VPN connection, managing any required {@code netd}
- * firewall rules.
+ * State tracker for legacy lockdown VPN. Watches for physical networks to be
+ * connected and kicks off VPN connection.
  */
 public class LockdownVpnTracker {
     private static final String TAG = "LockdownVpnTracker";
 
-    /** Number of VPN attempts before waiting for user intervention. */
-    private static final int MAX_ERROR_COUNT = 4;
-
     public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
 
     @NonNull private final Context mContext;
-    @NonNull private final ConnectivityService mConnService;
+    @NonNull private final ConnectivityManager mCm;
     @NonNull private final NotificationManager mNotificationManager;
     @NonNull private final Handler mHandler;
     @NonNull private final Vpn mVpn;
@@ -76,19 +70,73 @@
     @NonNull private final PendingIntent mConfigIntent;
     @NonNull private final PendingIntent mResetIntent;
 
+    @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback();
+    @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback();
+
+    private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+        private Network mNetwork = null;
+        private LinkProperties mLinkProperties = null;
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            boolean networkChanged = false;
+            if (!network.equals(mNetwork)) {
+                // The default network just changed.
+                mNetwork = network;
+                networkChanged = true;
+            }
+            mLinkProperties = lp;
+            // Backwards compatibility: previously, LockdownVpnTracker only responded to connects
+            // and disconnects, not LinkProperties changes on existing networks.
+            if (networkChanged) {
+                synchronized (mStateLock) {
+                    handleStateChangedLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            // The default network has gone down.
+            mNetwork = null;
+            mLinkProperties = null;
+            synchronized (mStateLock) {
+                handleStateChangedLocked();
+            }
+        }
+
+        public Network getNetwork() {
+            return mNetwork;
+        }
+
+        public LinkProperties getLinkProperties() {
+            return mLinkProperties;
+        }
+    }
+
+    private class VpnNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(Network network) {
+            synchronized (mStateLock) {
+                handleStateChangedLocked();
+            }
+        }
+        @Override
+        public void onLost(Network network) {
+            onAvailable(network);
+        }
+    }
+
     @Nullable
     private String mAcceptedEgressIface;
 
-    private int mErrorCount;
-
     public LockdownVpnTracker(@NonNull Context context,
-            @NonNull ConnectivityService connService,
             @NonNull Handler handler,
             @NonNull KeyStore keyStore,
             @NonNull Vpn vpn,
             @NonNull VpnProfile profile) {
         mContext = Objects.requireNonNull(context);
-        mConnService = Objects.requireNonNull(connService);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
         mHandler = Objects.requireNonNull(handler);
         mVpn = Objects.requireNonNull(vpn);
         mProfile = Objects.requireNonNull(profile);
@@ -110,24 +158,20 @@
      * connection when ready, or setting firewall rules once VPN is connected.
      */
     private void handleStateChangedLocked() {
-
-        final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
-        final LinkProperties egressProp = mConnService.getActiveLinkProperties();
+        final Network network = mDefaultNetworkCallback.getNetwork();
+        final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
 
         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
 
         // Restart VPN when egress network disconnected or changed
-        final boolean egressDisconnected = egressInfo == null
-                || State.DISCONNECTED.equals(egressInfo.getState());
+        final boolean egressDisconnected = (network == null);
         final boolean egressChanged = egressProp == null
                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
 
-        final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType();
         final String egressIface = (egressProp == null) ?
                 null : egressProp.getInterfaceName();
-        Log.d(TAG, "handleStateChanged: egress=" + egressType
-                + " " + mAcceptedEgressIface + "->" + egressIface);
+        Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface);
 
         if (egressDisconnected || egressChanged) {
             mAcceptedEgressIface = null;
@@ -138,46 +182,49 @@
             return;
         }
 
-        if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
-            EventLogTags.writeLockdownVpnError(egressType);
-        }
-
-        if (mErrorCount > MAX_ERROR_COUNT) {
-            showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
-
-        } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
-            if (mProfile.isValidLockdownProfile()) {
-                Log.d(TAG, "Active network connected; starting VPN");
-                EventLogTags.writeLockdownVpnConnecting(egressType);
-                showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
-
-                mAcceptedEgressIface = egressProp.getInterfaceName();
-                try {
-                    // Use the privileged method because Lockdown VPN is initiated by the system, so
-                    // no additional permission checks are necessary.
-                    mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
-                } catch (IllegalStateException e) {
-                    mAcceptedEgressIface = null;
-                    Log.e(TAG, "Failed to start VPN", e);
-                    showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
-                }
-            } else {
+        // At this point, |network| is known to be non-null.
+        if (!vpnInfo.isConnectedOrConnecting()) {
+            if (!mProfile.isValidLockdownProfile()) {
                 Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+                return;
             }
 
+            Log.d(TAG, "Active network connected; starting VPN");
+            showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
+
+            mAcceptedEgressIface = egressIface;
+            try {
+                // Use the privileged method because Lockdown VPN is initiated by the system, so
+                // no additional permission checks are necessary.
+                //
+                // Pass in the underlying network here because the legacy VPN is, in fact, tightly
+                // coupled to a given underlying network and cannot provide mobility. This makes
+                // things marginally more correct in two ways:
+                //
+                // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra
+                //    CONNECTED broadcast for the underlying network type. The underlying type comes
+                //    from here. LTT *could* assume that the underlying network is the default
+                //    network, but that might introduce a race condition if, say, the VPN starts
+                //    connecting on cell, but when the connection succeeds and the agent is
+                //    registered, the default network is now wifi.
+                // 2. If no underlying network is passed in, then CS will assume the underlying
+                //    network is the system default. So, if the VPN  is up and underlying network
+                //    (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
+                //    changed to match the new default network (e.g., cell).
+                mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
+            } catch (IllegalStateException e) {
+                mAcceptedEgressIface = null;
+                Log.e(TAG, "Failed to start VPN", e);
+                showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+            }
         } else if (vpnInfo.isConnected() && vpnConfig != null) {
             final String iface = vpnConfig.interfaze;
             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
 
             Log.d(TAG, "VPN connected using iface=" + iface
                     + ", sourceAddr=" + sourceAddrs.toString());
-            EventLogTags.writeLockdownVpnConnected(egressType);
             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
-
-            final NetworkInfo clone = new NetworkInfo(egressInfo);
-            augmentNetworkInfo(clone);
-            mConnService.sendConnectedBroadcast(clone);
         }
     }
 
@@ -192,7 +239,15 @@
 
         mVpn.setEnableTeardown(false);
         mVpn.setLockdown(true);
+        mCm.setLegacyLockdownVpnEnabled(true);
         handleStateChangedLocked();
+
+        mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+        final NetworkRequest vpnRequest = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(TRANSPORT_VPN)
+                .build();
+        mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler);
     }
 
     public void shutdown() {
@@ -205,20 +260,21 @@
         Log.d(TAG, "shutdownLocked()");
 
         mAcceptedEgressIface = null;
-        mErrorCount = 0;
 
         mVpn.stopVpnRunnerPrivileged();
         mVpn.setLockdown(false);
+        mCm.setLegacyLockdownVpnEnabled(false);
         hideNotification();
 
         mVpn.setEnableTeardown(true);
+        mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
+        mCm.unregisterNetworkCallback(mVpnNetworkCallback);
     }
 
     /**
      * Reset VPN lockdown tracker. Called by ConnectivityService when receiving
      * {@link #ACTION_LOCKDOWN_RESET} pending intent.
      */
-    @GuardedBy("mConnService.mVpns")
     public void reset() {
         Log.d(TAG, "reset()");
         synchronized (mStateLock) {
@@ -229,28 +285,6 @@
         }
     }
 
-    public void onNetworkInfoChanged() {
-        synchronized (mStateLock) {
-            handleStateChangedLocked();
-        }
-    }
-
-    public void onVpnStateChanged(NetworkInfo info) {
-        if (info.getDetailedState() == DetailedState.FAILED) {
-            mErrorCount++;
-        }
-        synchronized (mStateLock) {
-            handleStateChangedLocked();
-        }
-    }
-
-    public void augmentNetworkInfo(NetworkInfo info) {
-        if (info.isConnected()) {
-            final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
-            info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
-        }
-    }
-
     private void showNotification(int titleRes, int iconRes) {
         final Notification.Builder builder =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN)
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 769b781..78c1a95 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -197,6 +198,11 @@
     }
 
     @Override
+    protected void ensureFilters(ServiceInfo si, int userId) {
+        // nothing to filter
+    }
+
+    @Override
     protected void loadDefaultsFromConfig() {
         String defaultDndAccess = mContext.getResources().getString(
                 R.string.config_defaultDndAccessPackages);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 21537e6..bbdcac2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -189,6 +189,8 @@
 
     abstract protected void onServiceAdded(ManagedServiceInfo info);
 
+    abstract protected void ensureFilters(ServiceInfo si, int userId);
+
     protected List<ManagedServiceInfo> getServices() {
         synchronized (mMutex) {
             List<ManagedServiceInfo> services = new ArrayList<>(mServices);
@@ -1342,8 +1344,10 @@
             for (ComponentName component : add) {
                 try {
                     ServiceInfo info = mPm.getServiceInfo(component,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                            PackageManager.GET_META_DATA
+                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            userId);
                     if (info == null) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": service not found");
@@ -1356,7 +1360,7 @@
                     }
                     Slog.v(TAG,
                             "enabling " + getCaption() + " for " + userId + ": " + component);
-                    registerService(component, userId);
+                    registerService(info, userId);
                 } catch (RemoteException e) {
                     e.rethrowFromSystemServer();
                 }
@@ -1368,9 +1372,15 @@
      * Version of registerService that takes the name of a service component to bind to.
      */
     @VisibleForTesting
-    void registerService(final ComponentName name, final int userid) {
+    void registerService(final ServiceInfo si, final int userId) {
+        ensureFilters(si, userId);
+        registerService(si.getComponentName(), userId);
+    }
+
+    @VisibleForTesting
+    void registerService(final ComponentName cn, final int userId) {
         synchronized (mMutex) {
-            registerServiceLocked(name, userid);
+            registerServiceLocked(cn, userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1591e99..917be29 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -215,6 +215,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
@@ -9134,6 +9135,12 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            // nothing to filter; no user visible settings for types/packages like other
+            // listeners
+        }
+
+        @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             mListeners.unregisterService(removed.service, removed.userid);
@@ -9578,7 +9585,7 @@
 
     public class NotificationListeners extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
-        static final String TAG_REQUESTED_LISTENERS = "req_listeners";
+        static final String TAG_REQUESTED_LISTENERS = "request_listeners";
         static final String TAG_REQUESTED_LISTENER = "listener";
         static final String ATT_COMPONENT = "component";
         static final String ATT_TYPES = "types";
@@ -9701,28 +9708,6 @@
         }
 
         @Override
-        public void onUserUnlocked(int user) {
-            int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
-
-            final PackageManager pmWrapper = mContext.getPackageManager();
-            List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
-                    new Intent(getConfig().serviceInterface), flags, user);
-
-            for (ResolveInfo resolveInfo : installedServices) {
-                ServiceInfo info = resolveInfo.serviceInfo;
-
-                if (!getConfig().bindPermission.equals(info.permission)) {
-                    continue;
-                }
-                Pair key = Pair.create(info.getComponentName(), user);
-                if (!mRequestedNotificationListeners.containsKey(key)) {
-                    mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
-                }
-            }
-            super.onUserUnlocked(user);
-        }
-
-        @Override
         public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
             super.onPackagesChanged(removingPackage, pkgList, uidList);
 
@@ -9842,6 +9827,38 @@
             mRequestedNotificationListeners.put(pair, nlf);
         }
 
+        @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            Pair listener = Pair.create(si.getComponentName(), userId);
+            NotificationListenerFilter existingNlf =
+                    mRequestedNotificationListeners.get(listener);
+            if (existingNlf  == null) {
+                // no stored filters for this listener; see if they provided a default
+                if (si.metaData != null) {
+                    String typeList = si.metaData.getString(
+                            NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES);
+                    if (typeList != null) {
+                        int types = 0;
+                        String[] typeStrings = typeList.split(XML_SEPARATOR);
+                        for (int i = 0; i < typeStrings.length; i++) {
+                            if (TextUtils.isEmpty(typeStrings[i])) {
+                                continue;
+                            }
+                            try {
+                                types |= Integer.parseInt(typeStrings[i]);
+                            } catch (NumberFormatException e) {
+                                // skip
+                            }
+                        }
+
+                         NotificationListenerFilter nlf =
+                                 new NotificationListenerFilter(types, new ArraySet<>());
+                        mRequestedNotificationListeners.put(listener, nlf);
+                    }
+                }
+            }
+        }
+
         @GuardedBy("mNotificationLock")
         public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
             if (trim == TRIM_LIGHT) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 663fdee..0b52c2e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -115,10 +115,10 @@
         out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME.");
         out.println("  disable [--user USER_ID] PACKAGE[:NAME]");
         out.println("    Disable overlay within or owned by PACKAGE with optional unique NAME.");
-        out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE[:NAME]");
-        out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME and");
-        out.println("    disable all other overlays for its target package. If the --category");
-        out.println("    option is given, only disables other overlays in the same category.");
+        out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
+        out.println("    Enable overlay within or owned by PACKAGE and disable all other overlays");
+        out.println("    for its target package. If the --category option is given, only disables");
+        out.println("    other overlays in the same category.");
         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
         out.println("    Change the priority of the overlay to be just higher than");
         out.println("    the priority of PARENT If PARENT is the special keyword");
@@ -325,28 +325,12 @@
                     return 1;
             }
         }
-
-        final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
-        final OverlayInfo overlayInfo = mInterface.getOverlayInfoByIdentifier(overlay, userId);
-        if (overlayInfo == null) {
-            err.println("Error: Unable to get overlay info of: " + overlay);
-            return 1;
+        final String overlay = getNextArgRequired();
+        if (inCategory) {
+            return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
+        } else {
+            return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
         }
-
-        final List<OverlayInfo> overlaysForTarget =
-                mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
-        overlaysForTarget.remove(overlayInfo);
-        final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
-        for (final OverlayInfo disableOverlay : overlaysForTarget) {
-            if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
-                    || !disableOverlay.isMutable) {
-                continue;
-            }
-            builder.setEnabled(disableOverlay.getOverlayIdentifier(), false, userId);
-        }
-        builder.setEnabled(overlayInfo.getOverlayIdentifier(), true, userId);
-        mInterface.commit(builder.build());
-        return 0;
     }
 
     private int runSetPriority() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e91bb46..b06e84d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -935,6 +936,17 @@
             intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             intents[0].setSourceBounds(sourceBounds);
 
+            // Replace theme for splash screen
+            final int splashScreenThemeResId =
+                    mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(),
+                            callingPackage, packageName, shortcutId, targetUserId);
+            if (splashScreenThemeResId != 0) {
+                if (startActivityOptions == null) {
+                    startActivityOptions = new Bundle();
+                }
+                startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId);
+            }
+
             return startShortcutIntentsAsPublisher(
                     intents, packageName, featureId, startActivityOptions, targetUserId);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7b9cf73..fc18ddb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -193,7 +193,6 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
 import android.content.pm.ModuleInfo;
-import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
@@ -462,6 +461,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -2586,49 +2586,42 @@
                 Intent intent, int matchFlags, List<ResolveInfo> candidates,
                 CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
             final ArrayList<ResolveInfo> result = new ArrayList<>();
-            final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
-            final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
             final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
-            final int count = candidates.size();
-            // First, try to use linked apps. Partition the candidates into four lists:
-            // one for the final results, one for the "do not use ever", one for "undefined status"
-            // and finally one for "browser app type".
-            for (int n=0; n<count; n++) {
-                ResolveInfo info = candidates.get(n);
-                String packageName = info.activityInfo.packageName;
-                PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    // Add to the special match all list (Browser use case)
-                    if (info.handleAllWebDataURI) {
-                        matchAllList.add(info);
-                        continue;
-                    }
 
-                    boolean isAlways = mDomainVerificationManager
-                            .isApprovedForDomain(ps, intent, userId);
-                    if (isAlways) {
-                        alwaysList.add(info);
-                    } else {
-                        undefinedList.add(info);
-                    }
-                    continue;
+            final int count = candidates.size();
+            // First, try to use approved apps.
+            for (int n = 0; n < count; n++) {
+                ResolveInfo info = candidates.get(n);
+                // Add to the special match all list (Browser use case)
+                if (info.handleAllWebDataURI) {
+                    matchAllList.add(info);
                 }
             }
 
+            Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
+                    .filterToApprovedApp(intent, candidates, userId, mSettings::getPackageLPr);
+            List<ResolveInfo> approvedInfos = infosAndLevel.first;
+            Integer highestApproval = infosAndLevel.second;
+
             // We'll want to include browser possibilities in a few cases
             boolean includeBrowser = false;
 
-            // First try to add the "always" resolution(s) for the current user, if any
-            if (alwaysList.size() > 0) {
-                result.addAll(alwaysList);
+            // If no apps are approved for the domain, resolve only to browsers
+            if (approvedInfos.isEmpty()) {
+                // If the other profile has a result, include that and delegate to ResolveActivity
+                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
+                        > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
+                    result.add(xpDomainInfo.resolveInfo);
+                } else {
+                    includeBrowser = true;
+                }
             } else {
-                // Add all undefined apps as we want them to appear in the disambiguation dialog.
-                result.addAll(undefinedList);
-                // Maybe add one for the other profile.
-                if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
+                result.addAll(approvedInfos);
+
+                // If the other profile has an app that's of equal or higher approval, add it
+                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel >= highestApproval) {
                     result.add(xpDomainInfo.resolveInfo);
                 }
-                includeBrowser = true;
             }
 
             if (includeBrowser) {
@@ -2676,9 +2669,7 @@
                     }
                 }
 
-                // If there is nothing selected, add all candidates and remove the ones that the
-                //user
-                // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
+                // If there is nothing selected, add all candidates
                 if (result.size() == 0) {
                     result.addAll(candidates);
                 }
@@ -2780,10 +2771,12 @@
                             sourceUserId, parentUserId);
                 }
 
-                result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
-                        .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
+                result.highestApprovalLevel = Math.max(mDomainVerificationManager
+                        .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId),
+                        result.highestApprovalLevel);
             }
-            if (result != null && !result.wereAnyDomainsVerificationApproved) {
+            if (result != null && result.highestApprovalLevel
+                    <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
                 return null;
             }
             return result;
@@ -3026,9 +3019,10 @@
                     final String packageName = info.activityInfo.packageName;
                     final PackageSetting ps = mSettings.getPackageLPr(packageName);
                     if (ps.getInstantApp(userId)) {
-                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+                                userId)) {
                             if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "Instant app approvd for intent; pkg: "
+                                Slog.v(TAG, "Instant app approved for intent; pkg: "
                                         + packageName);
                             }
                             localInstantApp = info;
@@ -3953,7 +3947,8 @@
                 if (ps != null) {
                     // only check domain verification status if the app is not a browser
                     if (!info.handleAllWebDataURI) {
-                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+                                userId)) {
                             if (DEBUG_INSTANT) {
                                 Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
                                         + ", approved");
@@ -9369,8 +9364,8 @@
                     if (ri.activityInfo.applicationInfo.isInstantApp()) {
                         final String packageName = ri.activityInfo.packageName;
                         final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                        if (ps != null && mDomainVerificationManager
-                                .isApprovedForDomain(ps, intent, userId)) {
+                        if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps,
+                                intent, userId)) {
                             return ri;
                         }
                     }
@@ -9420,6 +9415,19 @@
     }
 
     /**
+     * Do NOT use for intent resolution filtering. That should be done with
+     * {@link DomainVerificationManagerInternal#filterToApprovedApp(Intent, List, int, Function)}.
+     *
+     * @return if the package is approved at any non-zero level for the domain in the intent
+     */
+    private static boolean hasAnyDomainApproval(
+            @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting,
+            @NonNull Intent intent, @UserIdInt int userId) {
+        return manager.approvalLevelForDomain(pkgSetting, intent, userId)
+                > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+    }
+
+    /**
      * Return true if the given list is not empty and all of its contents have
      * an activityInfo with the given package name.
      */
@@ -9862,7 +9870,7 @@
     private static class CrossProfileDomainInfo {
         /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
         ResolveInfo resolveInfo;
-        boolean wereAnyDomainsVerificationApproved;
+        int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
     }
 
     private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 5364cbf..a83a3f8 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -783,6 +783,10 @@
         incrementalStates.onStorageHealthStatusChanged(status);
     }
 
+    public long getFirstInstallTime() {
+        return firstInstallTime;
+    }
+
     protected PackageSettingBase updateFrom(PackageSettingBase other) {
         super.copyFrom(other);
         setPath(other.getPath());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 9b092c0..bb4ec16 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -112,6 +112,7 @@
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
     private static final String ATTR_ICON_URI = "icon-uri";
     private static final String ATTR_LOCUS_ID = "locus-id";
+    private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id";
 
     private static final String ATTR_PERSON_NAME = "name";
     private static final String ATTR_PERSON_URI = "uri";
@@ -1654,6 +1655,7 @@
         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
+        ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId());
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
@@ -1861,6 +1863,7 @@
         String bitmapPath;
         String iconUri;
         final String locusIdString;
+        int splashScreenThemeResId;
         int backupVersionCode;
         ArraySet<String> categories = null;
         ArrayList<Person> persons = new ArrayList<>();
@@ -1871,6 +1874,8 @@
         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
         titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
         titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
+        splashScreenThemeResId = ShortcutService.parseIntAttribute(parser,
+                ATTR_SPLASH_SCREEN_THEME_ID);
         text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
         textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
         textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
@@ -1964,7 +1969,8 @@
                 intents.toArray(new Intent[intents.size()]),
                 rank, extras, lastChangedTimestamp, flags,
                 iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons.toArray(new Person[persons.size()]), locusId);
+                disabledReason, persons.toArray(new Person[persons.size()]), locusId,
+                splashScreenThemeResId);
     }
 
     private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index d3aace1..c06f01a 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -383,6 +383,8 @@
             final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
             final int disabledMessageResId = sa.getResourceId(
                     R.styleable.Shortcut_shortcutDisabledMessage, 0);
+            final int splashScreenTheme = sa.getResourceId(
+                    R.styleable.Shortcut_splashScreenTheme, 0);
 
             if (TextUtils.isEmpty(id)) {
                 Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
@@ -404,7 +406,8 @@
                     disabledMessageResId,
                     rank,
                     iconResId,
-                    enabled);
+                    enabled,
+                    splashScreenTheme);
         } finally {
             sa.recycle();
         }
@@ -413,7 +416,7 @@
     private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
             @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
             int titleResId, int textResId, int disabledMessageResId,
-            int rank, int iconResId, boolean enabled) {
+            int rank, int iconResId, boolean enabled, int splashScreenTheme) {
 
         final int flags =
                 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -452,7 +455,8 @@
                 null, // icon Url
                 disabledReason,
                 null /* persons */,
-                null /* locusId */);
+                null /* locusId */,
+                splashScreenTheme);
     }
 
     private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 863e3fe5..4d8abea 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3214,6 +3214,32 @@
         }
 
         @Override
+        public int getShortcutStartingThemeResId(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull String shortcutId, int userId) {
+            Objects.requireNonNull(callingPackage, "callingPackage");
+            Objects.requireNonNull(packageName, "packageName");
+            Objects.requireNonNull(shortcutId, "shortcutId");
+
+            synchronized (mLock) {
+                throwIfUserLockedL(userId);
+                throwIfUserLockedL(launcherUserId);
+
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave();
+
+                final ShortcutPackage p = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (p == null) {
+                    return 0;
+                }
+
+                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
+                return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0;
+            }
+        }
+
+        @Override
         public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
                 @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull String shortcutId, int userId) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 37dfea4..5ee612b 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -125,8 +125,10 @@
     public static void validatePackageDexMetadata(AndroidPackage pkg)
             throws PackageParserException {
         Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
+        String packageName = pkg.getPackageName();
+        long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
         for (String dexMetadata : apkToDexMetadataList) {
-            DexMetadataHelper.validateDexMetadataFile(dexMetadata);
+            DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index e486f08..71e53d9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2593,6 +2593,7 @@
         ArraySet<String> isPrivilegedPermissionAllowlisted = null;
         ArraySet<String> shouldGrantSignaturePermission = null;
         ArraySet<String> shouldGrantInternalPermission = null;
+        ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
         final List<String> requestedPermissions = pkg.getRequestedPermissions();
         final int requestedPermissionsSize = requestedPermissions.size();
         for (int i = 0; i < requestedPermissionsSize; i++) {
@@ -2613,14 +2614,16 @@
                 isPrivilegedPermissionAllowlisted.add(permissionName);
             }
             if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
-                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
+                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted))) {
                 if (shouldGrantSignaturePermission == null) {
                     shouldGrantSignaturePermission = new ArraySet<>();
                 }
                 shouldGrantSignaturePermission.add(permissionName);
             }
             if (permission.isInternal()
-                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission)) {
+                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted)) {
                 if (shouldGrantInternalPermission == null) {
                     shouldGrantInternalPermission = new ArraySet<>();
                 }
@@ -2842,14 +2845,18 @@
                                             isPrivilegedPermissionAllowlisted, permName))
                                     && (CollectionUtils.contains(shouldGrantSignaturePermission,
                                             permName)
-                                            || ((bp.isDevelopment() || bp.isRole())
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))
                             || (bp.isInternal()
                                     && (!bp.isPrivileged() || CollectionUtils.contains(
                                             isPrivilegedPermissionAllowlisted, permName))
                                     && (CollectionUtils.contains(shouldGrantInternalPermission,
                                             permName)
-                                            || ((bp.isDevelopment() || bp.isRole())
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))) {
                         // Grant an install permission.
                         if (uidState.grantPermission(bp)) {
@@ -3374,10 +3381,23 @@
         if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
             return true;
         }
+        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+            return false;
+        }
+        // Updated system apps do not need to be allowlisted
+        if (packageSetting.getPkgState().isUpdatedSystemApp()) {
+            // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
+            // can be granted, because an updated system app may be in a shared UID, and in case a
+            // new privileged permission is requested by the updated system app but not the factory
+            // app, although this app and permission combination isn't in the allowlist and can't
+            // get the permission this way, other apps in the shared UID may still get it. A proper
+            // fix for this would be to perform the reconciliation by UID, but for now let's keep
+            // the old workaround working, which is to keep granted privileged permissions still
+            // granted.
+            return true;
+        }
         // Only enforce the allowlist on boot
-        if (!mSystemReady
-                // Updated system apps do not need to be allowlisted
-                && !packageSetting.getPkgState().isUpdatedSystemApp()) {
+        if (!mSystemReady) {
             final ApexManager apexManager = ApexManager.getInstance();
             final String containingApexPackageName =
                     apexManager.getActiveApexPackageNameContainingPackage(packageName);
@@ -3386,11 +3406,6 @@
                     MATCH_ACTIVE_PACKAGE));
             // Apps that are in updated apexs' do not need to be allowlisted
             if (!isInUpdatedApex) {
-                // it's only a reportable violation if the permission isn't explicitly
-                // denied
-                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
-                    return false;
-                }
                 Slog.w(TAG, "Privileged permission " + permissionName + " for package "
                         + packageName + " (" + pkg.getPath()
                         + ") not in privapp-permissions allowlist");
@@ -3468,7 +3483,8 @@
     }
 
     private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
-            @NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
+            @NonNull PackageSetting pkgSetting, @NonNull Permission bp,
+            @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) {
         boolean allowed = false;
         final boolean isPrivilegedPermission = bp.isPrivileged();
         final boolean isOemPermission = bp.isOem();
@@ -3480,11 +3496,18 @@
                 final PackageSetting disabledPs = mPackageManagerInt
                         .getDisabledSystemPackage(pkg.getPackageName());
                 final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
-                if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
-                        permissionName)) {
-                    allowed = (isPrivilegedPermission && disabledPkg.isPrivileged())
-                            || (isOemPermission && canGrantOemPermission(disabledPkg,
-                            permissionName));
+                if (disabledPkg != null
+                        && ((isPrivilegedPermission && disabledPkg.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(disabledPkg,
+                                permissionName)))) {
+                    if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
+                        allowed = true;
+                    } else {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName);
+                    }
                 }
             } else {
                 allowed = (isPrivilegedPermission && pkg.isPrivileged())
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index 36efb39..080de73 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -21,11 +21,8 @@
 import android.compat.annotation.EnabledSince;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedIntentInfo;
-import android.os.Binder;
 import android.os.Build;
 import android.util.ArraySet;
 import android.util.Patterns;
@@ -36,19 +33,31 @@
 
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class DomainVerificationCollector {
 
+    // The default domain name matcher doesn't account for wildcards, so prefix with *.
+    private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
+            Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
+
     @NonNull
     private final PlatformCompat mPlatformCompat;
 
     @NonNull
     private final SystemConfig mSystemConfig;
 
+    @NonNull
+    private final Matcher mDomainMatcher;
+
     public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
             @NonNull SystemConfig systemConfig) {
         mPlatformCompat = platformCompat;
         mSystemConfig = systemConfig;
+
+        // Cache the matcher to avoid calling into native on each check
+        mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher("");
     }
 
     /**
@@ -144,7 +153,10 @@
                 if (intent.handlesWebUris(false)) {
                     int authorityCount = intent.countDataAuthorities();
                     for (int index = 0; index < authorityCount; index++) {
-                        domains.add(intent.getDataAuthority(index).getHost());
+                        String host = intent.getDataAuthority(index).getHost();
+                        if (isValidHost(host)) {
+                            domains.add(host);
+                        }
                     }
                 }
             }
@@ -188,13 +200,22 @@
                 int authorityCount = intent.countDataAuthorities();
                 for (int index = 0; index < authorityCount; index++) {
                     String host = intent.getDataAuthority(index).getHost();
-                    // It's easy to misconfigure autoVerify intent filters, so to avoid
-                    // adding unintended hosts, check if the host is an HTTP domain.
-                    if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+                    if (isValidHost(host)) {
                         domains.add(host);
                     }
                 }
             }
         }
     }
+
+    /**
+     * It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts,
+     * check if the host is an HTTP domain. This applies for both legacy and modern versions of
+     * the API, which will strip invalid hosts from the legacy parsing result. This is done to
+     * improve the reliability of any legacy verifiers.
+     */
+    private boolean isValidHost(String host) {
+        mDomainMatcher.reset(host);
+        return mDomainMatcher.matches();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index 1925590..b3108c5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -38,8 +38,6 @@
 import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 
 import java.util.Arrays;
-import java.util.Objects;
-import java.util.Set;
 import java.util.function.Function;
 
 @SuppressWarnings("PointlessBooleanExpression")
@@ -202,8 +200,7 @@
                 printedHeader = true;
             }
 
-            boolean isLinkHandlingAllowed = userState == null
-                    || !userState.isDisallowLinkHandling();
+            boolean isLinkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
 
             writer.increaseIndent();
             writer.print("User ");
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 50fd6e3..5d4370a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -16,18 +16,22 @@
 
 package com.android.server.pm.verify.domain;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.os.Binder;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 
@@ -39,6 +43,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
@@ -48,6 +53,78 @@
     UUID DISABLED_ID = new UUID(0, 0);
 
     /**
+     * The app has not been approved for this domain and should never be able to open it through
+     * an implicit web intent.
+     */
+    int APPROVAL_LEVEL_NONE = 0;
+
+    /**
+     * The app has been approved through the legacy
+     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} and
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK}.
+     *
+     * This should be used as the cutoff for showing a picker if no better approved app exists
+     * during the legacy transition period.
+     *
+     * TODO(b/177923646): The legacy values can be removed once the Settings API changes are
+     *  shipped. These values are not stable, so just deleting the constant and shifting others is
+     *  fine.
+     */
+    int APPROVAL_LEVEL_LEGACY_ASK = 1;
+
+    /**
+     * The app has been approved through the legacy
+     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}.
+     */
+    int APPROVAL_LEVEL_LEGACY_ALWAYS = 1;
+
+    /**
+     * The app has been chosen by the user through
+     * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
+     * choice to use this app to open an unverified domain.
+     */
+    int APPROVAL_LEVEL_SELECTION = 2;
+
+    /**
+     * The app is approved through the digital asset link statement being hosted at the domain
+     * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
+     * the domain verification agent on device.
+     */
+    int APPROVAL_LEVEL_VERIFIED = 3;
+
+    /**
+     * The app has been installed as an instant app, which grants it total authority on the domains
+     * that it declares. It is expected that the package installer validate the domains the app
+     * declares against the digital asset link statements before allowing it to be installed.
+     *
+     * The user is still able to disable instant app link handling through
+     * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+     */
+    int APPROVAL_LEVEL_INSTANT_APP = 4;
+
+    /**
+     * Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
+     * which sorts packages by approval priority. A higher numerical value means the package should
+     * override all lower values. This means that comparison using less/greater than IS valid.
+     *
+     * Negative values are possible, although not implemented, reserved if explicit disable of a
+     * package for a domain needs to be tracked.
+     */
+    @IntDef({
+            APPROVAL_LEVEL_NONE,
+            APPROVAL_LEVEL_LEGACY_ASK,
+            APPROVAL_LEVEL_LEGACY_ALWAYS,
+            APPROVAL_LEVEL_SELECTION,
+            APPROVAL_LEVEL_VERIFIED,
+            APPROVAL_LEVEL_INSTANT_APP
+    })
+    @interface ApprovalLevel{}
+
+    /**
      * Generate a new domain set ID to be used for attaching new packages.
      */
     @NonNull
@@ -211,11 +288,28 @@
     DomainVerificationCollector getCollector();
 
     /**
-     * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
-     * This can be because the domain was auto-verified for the package, or if the user manually
-     * chose to enable the domain for the package.
+     * Filters the provided list down to the {@link ResolveInfo} objects that should be allowed
+     * to open the domain inside the {@link Intent}. It is possible for no packages represented in
+     * the list to be approved, in which case an empty list will be returned.
+     *
+     * @return the filtered list and the corresponding approval level
      */
-    boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+    @NonNull
+    Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction);
+
+    /**
+     * Check at what precedence a package resolving a URI is approved to takeover the domain.
+     * This can be because the domain was auto-verified for the package, or if the user manually
+     * chose to enable the domain for the package. If an app is auto-verified, it will be
+     * preferred over apps that were manually selected.
+     *
+     * NOTE: This should not be used for filtering intent resolution. See
+     * {@link #filterToApprovedApp(Intent, List, int, Function)} for that.
+     */
+    @ApprovalLevel
+    int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
             @UserIdInt int userId);
 
     /**
@@ -231,8 +325,7 @@
             throws IllegalArgumentException, NameNotFoundException;
 
 
-    interface Connection extends DomainVerificationEnforcer.Callback,
-            Function<String, PackageSetting> {
+    interface Connection extends DomainVerificationEnforcer.Callback {
 
         /**
          * Notify that a settings change has been made and that eventually
@@ -265,10 +358,5 @@
 
         @Nullable
         AndroidPackage getPackageLocked(@NonNull String pkgName);
-
-        @Override
-        default PackageSetting apply(@NonNull String pkgName) {
-            return getPackageSettingLocked(pkgName);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index 679f948..c864b29 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -53,7 +53,7 @@
 
     public static final String TAG_USER_STATE = "user-state";
     public static final String ATTR_USER_ID = "userId";
-    public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+    public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
     public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
     public static final String TAG_HOST = "host";
 
@@ -252,7 +252,7 @@
             return null;
         }
 
-        boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+        boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
         ArraySet<String> enabledHosts = new ArraySet<>();
 
         SettingsXml.ChildSection child = section.children();
@@ -260,7 +260,7 @@
             readEnabledHosts(child, enabledHosts);
         }
 
-        return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+        return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
     }
 
     private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -279,8 +279,8 @@
         try (SettingsXml.WriteSection section =
                      parentSection.startSection(TAG_USER_STATE)
                              .attribute(ATTR_USER_ID, userState.getUserId())
-                             .attribute(ATTR_DISALLOW_LINK_HANDLING,
-                                     userState.isDisallowLinkHandling())) {
+                             .attribute(ATTR_ALLOW_LINK_HANDLING,
+                                     userState.isLinkHandlingAllowed())) {
             ArraySet<String> enabledHosts = userState.getEnabledHosts();
             if (!enabledHosts.isEmpty()) {
                 try (SettingsXml.WriteSection enabledHostsSection =
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index fa03274..86a92d79 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.verify.domain;
 
+import static java.util.Collections.emptyList;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -26,6 +28,8 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
@@ -35,6 +39,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -62,6 +67,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
@@ -401,7 +407,7 @@
             }
 
             pkgState.getOrCreateUserSelectionState(userId)
-                    .setDisallowLinkHandling(!allowed);
+                    .setLinkHandlingAllowed(allowed);
         }
 
         mConnection.scheduleWriteSettings();
@@ -423,11 +429,11 @@
                         for (int userStateIndex = 0; userStateIndex < userStatesSize;
                                 userStateIndex++) {
                             userStates.valueAt(userStateIndex)
-                                    .setDisallowLinkHandling(!allowed);
+                                    .setLinkHandlingAllowed(allowed);
                         }
                     } else {
                         pkgState.getOrCreateUserSelectionState(userId)
-                                .setDisallowLinkHandling(!allowed);
+                                .setLinkHandlingAllowed(allowed);
                     }
                 }
 
@@ -440,7 +446,7 @@
                 }
 
                 pkgState.getOrCreateUserSelectionState(userId)
-                        .setDisallowLinkHandling(!allowed);
+                        .setLinkHandlingAllowed(allowed);
             }
         }
 
@@ -468,6 +474,17 @@
                 throw new InvalidDomainSetException(domainSetId, null,
                         InvalidDomainSetException.REASON_ID_INVALID);
             }
+
+            if (enabled) {
+                for (String domain : domains) {
+                    if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
+                            mConnection::getPackageSettingLocked).first.isEmpty()) {
+                        throw new InvalidDomainSetException(domainSetId, null,
+                                InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
+                    }
+                }
+            }
+
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
                     false /* forAutoVerify */, callingUid, userId);
             DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
@@ -589,10 +606,10 @@
                 hostToUserSelectionMap.put(domains.valueAt(index), false);
             }
 
-            boolean openVerifiedLinks = false;
             DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            boolean linkHandlingAllowed = true;
             if (userState != null) {
-                openVerifiedLinks = !userState.isDisallowLinkHandling();
+                linkHandlingAllowed = userState.isLinkHandlingAllowed();
                 ArraySet<String> enabledHosts = userState.getEnabledHosts();
                 int hostsSize = enabledHosts.size();
                 for (int index = 0; index < hostsSize; index++) {
@@ -601,7 +618,7 @@
             }
 
             return new DomainVerificationUserSelection(pkgState.getId(), packageName,
-                    UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+                    UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
         }
     }
 
@@ -683,7 +700,7 @@
                     ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
                     newEnabledHosts.retainAll(newWebDomains);
                     DomainVerificationUserState newUserState = new DomainVerificationUserState(
-                            userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+                            userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
                     newUserStates.put(userId, newUserState);
                 }
             }
@@ -910,7 +927,7 @@
         // This method is only used by DomainVerificationShell, which doesn't lock PMS, so it's
         // safe to pass mConnection directly here and lock PMS. This method is not exposed
         // to the general system server/PMS.
-        printState(writer, packageName, userId, mConnection);
+        printState(writer, packageName, userId, mConnection::getPackageSettingLocked);
     }
 
     @Override
@@ -919,7 +936,7 @@
             @Nullable Function<String, PackageSetting> pkgSettingFunction)
             throws NameNotFoundException {
         if (pkgSettingFunction == null) {
-            pkgSettingFunction = mConnection;
+            pkgSettingFunction = mConnection::getPackageSettingLocked;
         }
 
         synchronized (mLock) {
@@ -1156,46 +1173,211 @@
         mConnection.scheduleWriteSettings();
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * Resolving an Intent to an approved app happens in stages:
+     * <ol>
+     *     <li>Find all non-zero approved packages for the {@link Intent}'s domain</li>
+     *     <li>Filter to packages with the highest approval level, see {@link ApprovalLevel}</li>
+     *     <li>Filter out {@link ResolveInfo}s that don't match that approved packages</li>
+     *     <li>Take the approved packages with the latest install time</li>
+     *     <li>Take the ResolveInfo representing the Activity declared last in the manifest</li>
+     *     <li>Return remaining results if any exist</li>
+     * </ol>
+     */
+    @NonNull
     @Override
-    public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+    public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        String domain = intent.getData().getHost();
+
+        // Collect package names
+        ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+        int infosSize = infos.size();
+        for (int index = 0; index < infosSize; index++) {
+            packageApprovals.put(infos.get(index).getComponentInfo().packageName,
+                    APPROVAL_LEVEL_NONE);
+        }
+
+        // Find all approval levels
+        int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+                pkgSettingFunction);
+        if (highestApproval == APPROVAL_LEVEL_NONE) {
+            return Pair.create(emptyList(), highestApproval);
+        }
+
+        // Filter to highest, non-zero packages
+        ArraySet<String> approvedPackages = new ArraySet<>();
+        for (int index = 0; index < infosSize; index++) {
+            if (packageApprovals.valueAt(index) == highestApproval) {
+                approvedPackages.add(packageApprovals.keyAt(index));
+            }
+        }
+
+        ArraySet<String> filteredPackages = new ArraySet<>();
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // To maintain legacy behavior while the Settings API is not implemented,
+            // show the chooser if all approved apps are marked ask, skipping the
+            // last app, last declaration filtering.
+            filteredPackages.addAll(approvedPackages);
+        } else {
+            // Filter to last installed package
+            long latestInstall = Long.MIN_VALUE;
+            int approvedSize = approvedPackages.size();
+            for (int index = 0; index < approvedSize; index++) {
+                String packageName = approvedPackages.valueAt(index);
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+                long installTime = pkgSetting.getFirstInstallTime();
+                if (installTime > latestInstall) {
+                    latestInstall = installTime;
+                    filteredPackages.clear();
+                    filteredPackages.add(packageName);
+                } else if (installTime == latestInstall) {
+                    filteredPackages.add(packageName);
+                }
+            }
+        }
+
+        // Filter to approved ResolveInfos
+        ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
+        for (int index = 0; index < infosSize; index++) {
+            ResolveInfo info = infos.get(index);
+            String packageName = info.getComponentInfo().packageName;
+            if (filteredPackages.contains(packageName)) {
+                List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
+                if (infosPerPackage == null) {
+                    infosPerPackage = new ArrayList<>();
+                    approvedInfos.put(packageName, infosPerPackage);
+                }
+                infosPerPackage.add(info);
+            }
+        }
+
+        List<ResolveInfo> finalList;
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // If legacy ask, skip the last declaration filtering
+            finalList = new ArrayList<>();
+            int size = approvedInfos.size();
+            for (int index = 0; index < size; index++) {
+                finalList.addAll(approvedInfos.valueAt(index));
+            }
+        } else {
+            // Find the last declared ResolveInfo per package
+            finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+        }
+
+        return Pair.create(finalList, highestApproval);
+    }
+
+    /**
+     * @return highest approval level found
+     */
+    private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+            @NonNull String domain, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = APPROVAL_LEVEL_NONE;
+        int size = inputMap.size();
+        for (int index = 0; index < size; index++) {
+            String packageName = inputMap.keyAt(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+                continue;
+            }
+            int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+            highestApproval = Math.max(highestApproval, approval);
+            inputMap.setValueAt(index, approval);
+        }
+
+        return highestApproval;
+    }
+
+    @NonNull
+    private List<ResolveInfo> filterToLastDeclared(
+            @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
+
+        int inputSize = inputMap.size();
+        for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
+            String packageName = inputMap.keyAt(inputIndex);
+            List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                continue;
+            }
+
+            ResolveInfo result = null;
+            int highestIndex = -1;
+            int infosSize = infos.size();
+            for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
+                ResolveInfo info = infos.get(infoIndex);
+                List<ParsedActivity> activities = pkg.getActivities();
+                int activitiesSize = activities.size();
+                for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+                    if (Objects.equals(activities.get(activityIndex).getComponentName(),
+                            info.getComponentInfo().getComponentName())) {
+                        if (activityIndex > highestIndex) {
+                            highestIndex = activityIndex;
+                            result = info;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            // Shouldn't be null, but might as well be safe
+            if (result != null) {
+                finalList.add(result);
+            }
+        }
+
+        return finalList;
+    }
+
+    @Override
+    public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
             @UserIdInt int userId) {
         String packageName = pkgSetting.name;
         if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, intent, userId, false, "not valid intent");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
 
-        String host = intent.getData().getHost();
+        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
+    }
+
+    /**
+     * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
+     *                    otherwise.
+     */
+    private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
+            @UserIdInt int userId, @NonNull Object debugObject) {
+        String packageName = pkgSetting.name;
         final AndroidPackage pkg = pkgSetting.getPkg();
 
         // Should never be null, but if it is, skip this and assume that v2 is enabled
-        if (pkg != null) {
-            // To allow an instant app to immediately open domains after being installed by the
-            // user, auto approve them for any declared autoVerify domains.
-            if (pkgSetting.getInstantApp(userId)
-                    && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
-                return true;
-            }
-
-            if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
-                int legacyState = mLegacySettings.getUserState(packageName, userId);
-                switch (legacyState) {
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
-                        // If nothing specifically set, assume v2 rules
-                        break;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
-                        // With v2 split into 2 lists, always and undefined, the concept of whether
-                        // or not to ask is irrelevant. Assume the user wants this application to
-                        // open the domain.
-                        return true;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
-                        // Never has the same semantics are before
-                        return false;
-                }
+        if (pkg != null && !DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg,
+                SETTINGS_API_V2)) {
+            switch (mLegacySettings.getUserState(packageName, userId)) {
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+                    // If nothing specifically set, assume v2 rules
+                    break;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+                    return APPROVAL_LEVEL_NONE;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+                    return APPROVAL_LEVEL_LEGACY_ASK;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                    return APPROVAL_LEVEL_LEGACY_ALWAYS;
             }
         }
 
@@ -1203,59 +1385,76 @@
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "pkgState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+            if (userState != null && !userState.isLinkHandlingAllowed()) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, false,
+                            "link handling not allowed");
+                }
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            // The instant app branch must be run after the link handling check,
+            // since that should also disable instant apps if toggled
+            if (pkg != null) {
+                // To allow an instant app to immediately open domains after being installed by the
+                // user, auto approve them for any declared autoVerify domains.
+                if (pkgSetting.getInstantApp(userId)
+                        && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+                    return APPROVAL_LEVEL_INSTANT_APP;
+                }
             }
 
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            // Check if the exact host matches
+            Integer state = stateMap.get(host);
+            if (state != null && DomainVerificationManager.isStateVerified(state)) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, true,
+                            "host verified exactly");
+                }
+                return APPROVAL_LEVEL_VERIFIED;
+            }
 
-            // Only allow autoVerify approval if the user hasn't disabled it
-            if (userState == null || !userState.isDisallowLinkHandling()) {
-                // Check if the exact host matches
-                Integer state = stateMap.get(host);
-                if (state != null && DomainVerificationManager.isStateVerified(state)) {
-                    if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true, "host verified exactly");
-                    }
-                    return true;
+            // Otherwise see if the host matches a verified domain by wildcard
+            int stateMapSize = stateMap.size();
+            for (int index = 0; index < stateMapSize; index++) {
+                if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+                    continue;
                 }
 
-                // Otherwise see if the host matches a verified domain by wildcard
-                int stateMapSize = stateMap.size();
-                for (int index = 0; index < stateMapSize; index++) {
-                    if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
-                        continue;
+                String domain = stateMap.keyAt(index);
+                if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+                    if (DEBUG_APPROVAL) {
+                        debugApproval(packageName, debugObject, userId, true,
+                                "host verified by wildcard");
                     }
-
-                    String domain = stateMap.keyAt(index);
-                    if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
-                        if (DEBUG_APPROVAL) {
-                            debugApproval(packageName, intent, userId, true,
-                                    "host verified by wildcard");
-                        }
-                        return true;
-                    }
+                    return APPROVAL_LEVEL_VERIFIED;
                 }
             }
 
             // Check user state if available
             if (userState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "userState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "userState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
             }
 
             // See if the user has approved the exact host
             ArraySet<String> enabledHosts = userState.getEnabledHosts();
             if (enabledHosts.contains(host)) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, true,
+                    debugApproval(packageName, debugObject, userId, true,
                             "host enabled by user exactly");
                 }
-                return true;
+                return APPROVAL_LEVEL_SELECTION;
             }
 
             // See if the host matches a user selection by wildcard
@@ -1264,24 +1463,85 @@
                 String domain = enabledHosts.valueAt(index);
                 if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
                     if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true,
+                        debugApproval(packageName, debugObject, userId, true,
                                 "host enabled by user through wildcard");
                     }
-                    return true;
+                    return APPROVAL_LEVEL_SELECTION;
                 }
             }
 
             if (DEBUG_APPROVAL) {
-                debugApproval(packageName, intent, userId, false, "not approved");
+                debugApproval(packageName, debugObject, userId, false, "not approved");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
     }
 
-    private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+    /**
+     * @return the filtered list paired with the corresponding approval level
+     */
+    @NonNull
+    private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
+            @UserIdInt int userId, int minimumApproval,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = minimumApproval;
+        List<String> approvedPackages = emptyList();
+
+        synchronized (mLock) {
+            final int size = mAttachedPkgStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                String packageName = pkgState.getPackageName();
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+
+                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+                if (level < minimumApproval) {
+                    continue;
+                }
+
+                if (level > highestApproval) {
+                    approvedPackages.clear();
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                    highestApproval = level;
+                } else if (level == highestApproval) {
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                }
+            }
+        }
+
+        if (approvedPackages.isEmpty()) {
+            return Pair.create(approvedPackages, APPROVAL_LEVEL_NONE);
+        }
+
+        List<String> filteredPackages = new ArrayList<>();
+        long latestInstall = Long.MIN_VALUE;
+        final int approvedSize = approvedPackages.size();
+        for (int index = 0; index < approvedSize; index++) {
+            String packageName = approvedPackages.get(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                continue;
+            }
+            long installTime = pkgSetting.getFirstInstallTime();
+            if (installTime > latestInstall) {
+                latestInstall = installTime;
+                filteredPackages.clear();
+                filteredPackages.add(packageName);
+            } else if (installTime == latestInstall) {
+                filteredPackages.add(packageName);
+            }
+        }
+
+        return Pair.create(filteredPackages, highestApproval);
+    }
+
+    private void debugApproval(@NonNull String packageName, @NonNull Object debugObject,
             @UserIdInt int userId, boolean approved, @NonNull String reason) {
         String approvalString = approved ? "approved" : "denied";
-        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
-                + " for user " + userId + ": " + reason);
+        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+                + debugObject + " for user " + userId + ": " + reason);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index 073967e..a8e937c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -23,7 +23,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -229,14 +228,14 @@
             DomainVerificationUserState oldUserState =
                     oldSelectionStates.get(UserHandle.USER_SYSTEM);
 
-            boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+            boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
             if (oldUserState == null) {
                 oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
-                        newEnabledHosts, disallowLinkHandling);
+                        newEnabledHosts, linkHandlingAllowed);
                 oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
             } else {
                 oldUserState.addHosts(newEnabledHosts)
-                        .setDisallowLinkHandling(disallowLinkHandling);
+                        .setLinkHandlingAllowed(linkHandlingAllowed);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
index 8e82608..2246864 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -25,10 +25,10 @@
 import java.util.Set;
 
 /**
- * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
- * that domain when a web URL Intent is sent ft.
+ * Tracks which domains have been explicitly enabled by the user, allowing it to open that domain
+ * when a web URL Intent is sent and the application is the highest priority for that domain.
  */
-@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
 public class DomainVerificationUserState {
 
     @UserIdInt
@@ -38,8 +38,10 @@
     @NonNull
     private final ArraySet<String> mEnabledHosts;
 
-    /** Whether to disallow this package from automatically opening links by auto verification. */
-    private boolean mDisallowLinkHandling;
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
+    private boolean mLinkHandlingAllowed = true;
 
     public DomainVerificationUserState(@UserIdInt int userId) {
         mUserId = userId;
@@ -67,13 +69,15 @@
     }
 
 
+
     // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
+    // /verify/domain/models/DomainVerificationUserState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -85,19 +89,21 @@
      *
      * @param enabledHosts
      *   List of domains which have been enabled by the user. *
+     * @param linkHandlingAllowed
+     *   Whether to allow this package to automatically open links by auto verification.
      */
     @DataClass.Generated.Member
     public DomainVerificationUserState(
             @UserIdInt int userId,
             @NonNull ArraySet<String> enabledHosts,
-            boolean disallowLinkHandling) {
+            boolean linkHandlingAllowed) {
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
                 UserIdInt.class, null, mUserId);
         this.mEnabledHosts = enabledHosts;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mEnabledHosts);
-        this.mDisallowLinkHandling = disallowLinkHandling;
+        this.mLinkHandlingAllowed = linkHandlingAllowed;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -115,14 +121,20 @@
         return mEnabledHosts;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public boolean isDisallowLinkHandling() {
-        return mDisallowLinkHandling;
+    public boolean isLinkHandlingAllowed() {
+        return mLinkHandlingAllowed;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
-        mDisallowLinkHandling = value;
+    public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+        mLinkHandlingAllowed = value;
         return this;
     }
 
@@ -135,7 +147,7 @@
         return "DomainVerificationUserState { " +
                 "userId = " + mUserId + ", " +
                 "enabledHosts = " + mEnabledHosts + ", " +
-                "disallowLinkHandling = " + mDisallowLinkHandling +
+                "linkHandlingAllowed = " + mLinkHandlingAllowed +
         " }";
     }
 
@@ -154,7 +166,7 @@
         return true
                 && mUserId == that.mUserId
                 && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
-                && mDisallowLinkHandling == that.mDisallowLinkHandling;
+                && mLinkHandlingAllowed == that.mLinkHandlingAllowed;
     }
 
     @Override
@@ -166,15 +178,15 @@
         int _hash = 1;
         _hash = 31 * _hash + mUserId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
-        _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+        _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1608234273324L,
+            time = 1612894390039L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
-            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mDisallowLinkHandling\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mLinkHandlingAllowed\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 9389e63..a804065 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -30,7 +30,6 @@
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.os.Process;
 import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -45,6 +44,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -168,22 +168,58 @@
                     return true;
                 }
 
-                Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet());
-                successfulDomains.removeAll(response.failedDomains);
+                AndroidPackage pkg = mConnection.getPackage(packageName);
+                if (pkg == null) {
+                    return true;
+                }
+
+                ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains);
+                Map<String, Integer> hostToStateMap = info.getHostToStateMap();
+                Set<String> hostKeySet = hostToStateMap.keySet();
+                ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet);
+                successfulDomains.removeAll(failedDomains);
+
+                // v1 doesn't handle wildcard domains, so check them here for the verifier
+                int size = successfulDomains.size();
+                for (int index = size - 1; index >= 0; index--) {
+                    String domain = successfulDomains.valueAt(index);
+                    if (domain.startsWith("*.")) {
+                        String nonWildcardDomain = domain.substring(2);
+                        if (failedDomains.contains(nonWildcardDomain)) {
+                            failedDomains.add(domain);
+                            successfulDomains.removeAt(index);
+
+                            // It's possible to declare a wildcard without declaring its
+                            // non-wildcard equivalent, so if it wasn't originally declared,
+                            // remove the transformed domain from the failed set. Otherwise the
+                            // manager will not accept the failed set as it contains an undeclared
+                            // domain.
+                            if (!hostKeySet.contains(nonWildcardDomain)) {
+                                failedDomains.remove(nonWildcardDomain);
+                            }
+                        }
+                    }
+                }
 
                 int callingUid = response.callingUid;
-                try {
-                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
-                            successfulDomains, DomainVerificationState.STATE_SUCCESS);
-                } catch (DomainVerificationManager.InvalidDomainSetException
-                        | PackageManager.NameNotFoundException ignored) {
+                if (!successfulDomains.isEmpty()) {
+                    try {
+                        mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                                successfulDomains, DomainVerificationState.STATE_SUCCESS);
+                    } catch (DomainVerificationManager.InvalidDomainSetException
+                            | PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
+                    }
                 }
-                try {
-                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
-                            new ArraySet<>(response.failedDomains),
-                            DomainVerificationState.STATE_LEGACY_FAILURE);
-                } catch (DomainVerificationManager.InvalidDomainSetException
-                        | PackageManager.NameNotFoundException ignored) {
+
+                if (!failedDomains.isEmpty()) {
+                    try {
+                        mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                                failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
+                    } catch (DomainVerificationManager.InvalidDomainSetException
+                            | PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
+                    }
                 }
 
                 return true;
@@ -235,7 +271,21 @@
         // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
         // not the version of the verification agent on device.
         ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
-        return TextUtils.join(" ", domains);
+
+        // v1 doesn't handle wildcard domains, so transform them here to the root
+        StringBuilder builder = new StringBuilder();
+        int size = domains.size();
+        for (int index = 0; index < size; index++) {
+            if (index > 0) {
+                builder.append(" ");
+            }
+            String domain = domains.valueAt(index);
+            if (domain.startsWith("*.")) {
+                domain = domain.substring(2);
+            }
+            builder.append(domain);
+        }
+        return builder.toString();
     }
 
     private static class Response {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0d43600..15c72b3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -139,6 +139,7 @@
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuBpfTracking;
 import com.android.internal.os.KernelCpuThreadReader;
 import com.android.internal.os.KernelCpuThreadReaderDiff;
 import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
@@ -1455,7 +1456,7 @@
     }
 
     private void registerCpuTimePerClusterFreq() {
-        if (KernelCpuTotalBpfMapReader.isSupported()) {
+        if (KernelCpuBpfTracking.isSupported()) {
             int tagId = FrameworkStatsLog.CPU_TIME_PER_CLUSTER_FREQ;
             PullAtomMetadata metadata = new PullAtomMetadata.Builder()
                     .setAdditiveFields(new int[] {3})
@@ -1649,17 +1650,18 @@
     }
 
     private void registerCpuCyclesPerThreadGroupCluster() {
-        // TODO(b/173227907): Register only when supported.
-        int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
-        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
-                .setAdditiveFields(new int[] {3, 4})
-                .build();
-        mStatsManager.setPullAtomCallback(
-                tagId,
-                metadata,
-                DIRECT_EXECUTOR,
-                mStatsCallbackImpl
-        );
+        if (KernelCpuBpfTracking.isSupported()) {
+            int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
+            PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                    .setAdditiveFields(new int[] {3, 4})
+                    .build();
+            mStatsManager.setPullAtomCallback(
+                    tagId,
+                    metadata,
+                    DIRECT_EXECUTOR,
+                    mStatsCallbackImpl
+            );
+        }
     }
 
     int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
@@ -1718,7 +1720,7 @@
         }
         for (int cluster = 0; cluster < clusters; ++cluster) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    atomTag, threadGroup, cluster, aggregatedCycles[cluster],
+                    atomTag, threadGroup, cluster, aggregatedCycles[cluster] / 1_000_000L,
                     aggregatedTimesUs[cluster] / 1_000));
         }
     }
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index eb4a050..0087c0c 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -158,6 +158,29 @@
     }
 
     /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long getAnrDelayMillis(String packageName, int uid)
+            throws ExternalStorageServiceException {
+        synchronized (mLock) {
+            int size = mConnections.size();
+            for (int i = 0; i < size; i++) {
+                int key = mConnections.keyAt(i);
+                StorageUserConnection connection = mConnections.get(key);
+                if (connection != null) {
+                    long delay = connection.getAnrDelayMillis(packageName, uid);
+                    if (delay > 0) {
+                        return delay;
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Removes and returns the {@link StorageUserConnection} for {@code vol}.
      *
      * Does nothing if {@link #shouldHandle} is {@code false}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 13cceee..709d558 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.server.storage;
 
+import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS;
 import static android.service.storage.ExternalStorageService.EXTRA_ERROR;
 import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE;
 import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE;
@@ -143,6 +144,24 @@
     }
 
     /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long getAnrDelayMillis(String packageName, int uid)
+            throws ExternalStorageServiceException {
+        synchronized (mSessionsLock) {
+            for (String sessionId : mSessions.keySet()) {
+                long delay = mActiveConnection.getAnrDelayMillis(packageName, uid);
+                if (delay > 0) {
+                    return delay;
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Removes a session without ending it or waiting for exit.
      *
      * This should only be used if the session has certainly been ended because the volume was
@@ -234,6 +253,9 @@
         @GuardedBy("mLock")
         private final ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>();
 
+        @GuardedBy("mLock")
+        private final ArrayList<CompletableFuture<Long>> mOutstandingTimeoutOps = new ArrayList<>();
+
         @Override
         public void close() {
             ServiceConnection oldConnection = null;
@@ -250,6 +272,9 @@
                 for (CompletableFuture<Void> op : mOutstandingOps) {
                     op.cancel(true);
                 }
+                for (CompletableFuture<Long> op : mOutstandingTimeoutOps) {
+                    op.cancel(true);
+                }
                 mOutstandingOps.clear();
             }
 
@@ -264,27 +289,44 @@
             }
         }
 
-        private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception {
-            CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+        private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
             CompletableFuture<Void> opFuture = new CompletableFuture<>();
+            RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
+
+            waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
+                    DEFAULT_REMOTE_TIMEOUT_SECONDS);
+        }
+
+        private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception {
+            CompletableFuture<Long> opFuture = new CompletableFuture<>();
+            RemoteCallback callback =
+                    new RemoteCallback(result -> setTimeoutResult(result, opFuture));
+
+            return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps,
+                    1 /* timeoutSeconds */);
+        }
+
+        private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
+                CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
+                long timeoutSeconds) throws Exception {
+            CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
 
             try {
                 synchronized (mLock) {
-                    mOutstandingOps.add(opFuture);
+                    outstandingOps.add(opFuture);
                 }
-                serviceFuture.thenCompose(service -> {
+                return serviceFuture.thenCompose(service -> {
                     try {
-                        asyncCall.run(service,
-                                new RemoteCallback(result -> setResult(result, opFuture)));
+                        asyncCall.run(service, callback);
                     } catch (RemoteException e) {
                         opFuture.completeExceptionally(e);
                     }
 
                     return opFuture;
-                }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+                }).get(timeoutSeconds, TimeUnit.SECONDS);
             } finally {
                 synchronized (mLock) {
-                    mOutstandingOps.remove(opFuture);
+                    outstandingOps.remove(opFuture);
                 }
             }
         }
@@ -292,9 +334,9 @@
         public void startSession(Session session, ParcelFileDescriptor fd)
                 throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) -> service.startSession(session.sessionId,
+                waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId,
                         FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
-                        fd, session.upperPath, session.lowerPath, callback));
+                                fd, session.upperPath, session.lowerPath, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to start session: " + session, e);
             } finally {
@@ -308,7 +350,7 @@
 
         public void endSession(Session session) throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.endSession(session.sessionId, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to end session: " + session, e);
@@ -319,7 +361,7 @@
         public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws
                 ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.notifyVolumeStateChanged(sessionId, vol, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to notify volume state changed "
@@ -330,7 +372,7 @@
         public void freeCache(String sessionId, String volumeUuid, long bytes)
                 throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.freeCache(sessionId, volumeUuid, bytes, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to free " + bytes
@@ -338,6 +380,27 @@
             }
         }
 
+        public long getAnrDelayMillis(String packgeName, int uid)
+                throws ExternalStorageServiceException {
+            try {
+                return waitForAsyncLong((service, callback) ->
+                        service.getAnrDelayMillis(packgeName, uid, callback));
+            } catch (Exception e) {
+                throw new ExternalStorageServiceException("Failed to notify app not responding: "
+                        + packgeName, e);
+            }
+        }
+
+        private void setTimeoutResult(Bundle result, CompletableFuture<Long> future) {
+            ParcelableException ex = result.getParcelable(EXTRA_ERROR);
+            if (ex != null) {
+                future.completeExceptionally(ex);
+            } else {
+                long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS);
+                future.complete(timeoutMs);
+            }
+        }
+
         private void setResult(Bundle result, CompletableFuture<Void> future) {
             ParcelableException ex = result.getParcelable(EXTRA_ERROR);
             if (ex != null) {
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2503e81..8aab3a66 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
@@ -58,6 +59,9 @@
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -65,6 +69,7 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -120,15 +125,34 @@
  * +----------------------------+
  * </pre>
  *
+ * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link
+ * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link
+ * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the
+ * lack of WakeLocks).
+ *
+ * <p>Any attempt to remove messages from the Handler should be done using {@link
+ * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
+ * no messages remain in the Handler queue.
+ *
  * @hide
  */
 public class VcnGatewayConnection extends StateMachine {
     private static final String TAG = VcnGatewayConnection.class.getSimpleName();
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
+
     private static final int[] MERGED_CAPABILITIES =
             new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
-
-    private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
     private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
 
     private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
@@ -137,7 +161,8 @@
     private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
-    private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int TEARDOWN_TIMEOUT_SECONDS = 5;
@@ -412,11 +437,26 @@
     @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
     @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull private final Dependencies mDeps;
-
     @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
 
     @NonNull private final IpSecManager mIpSecManager;
-    @NonNull private final IpSecTunnelInterface mTunnelIface;
+
+    @Nullable private IpSecTunnelInterface mTunnelIface = null;
+
+    /**
+     * WakeLock to be held when processing messages on the Handler queue.
+     *
+     * <p>Used to prevent the device from going to sleep while there are VCN-related events to
+     * process for this VcnGatewayConnection.
+     *
+     * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the
+     * Handler queue have been processed, the WakeLock can be released and cleared.
+     *
+     * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send
+     * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock
+     * before enquing the delayed event to the Handler.
+     */
+    @NonNull private final VcnWakeLock mWakeLock;
 
     /** Running state of this VcnGatewayConnection. */
     private boolean mIsRunning = true;
@@ -482,6 +522,10 @@
      */
     private NetworkAgent mNetworkAgent;
 
+    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
+    @Nullable private WakeupMessage mDisconnectRequestAlarm;
+    @Nullable private WakeupMessage mRetryTimeoutAlarm;
+
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
@@ -517,6 +561,9 @@
 
         mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
 
+        mWakeLock =
+                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
                         mVcnContext,
@@ -526,20 +573,6 @@
                         mUnderlyingNetworkTrackerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
 
-        IpSecTunnelInterface iface;
-        try {
-            iface =
-                    mIpSecManager.createIpSecTunnelInterface(
-                            DUMMY_ADDR, DUMMY_ADDR, new Network(-1));
-        } catch (IOException | ResourceUnavailableException e) {
-            teardownAsynchronously();
-            mTunnelIface = null;
-
-            return;
-        }
-
-        mTunnelIface = iface;
-
         addState(mDisconnectedState);
         addState(mDisconnectingState);
         addState(mConnectingState);
@@ -557,7 +590,7 @@
      * <p>Once torn down, this VcnTunnel CANNOT be started again.
      */
     public void teardownAsynchronously() {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_DISCONNECT_REQUESTED,
                 TOKEN_ALL,
                 new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
@@ -573,6 +606,12 @@
             mTunnelIface.close();
         }
 
+        releaseWakeLock();
+
+        cancelTeardownTimeoutAlarm();
+        cancelDisconnectRequestAlarm();
+        cancelRetryTimeoutAlarm();
+
         mUnderlyingNetworkTracker.teardown();
     }
 
@@ -589,79 +628,300 @@
         mLastSnapshot = snapshot;
         mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
 
-        sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
     private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
         @Override
         public void onSelectedUnderlyingNetworkChanged(
                 @Nullable UnderlyingNetworkRecord underlying) {
+            // TODO(b/180132994): explore safely removing this Thread check
+            mVcnContext.ensureRunningOnLooperThread();
+
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
             // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
             // timeout.
             if (underlying == null) {
-                sendMessageDelayed(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST),
-                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
-            } else if (getHandler() != null) {
-                // Cancel any existing disconnect due to loss of underlying network
-                // getHandler() can return null if the state machine has already quit. Since this is
-                // called from other classes, this condition must be verified.
-
-                getHandler()
-                        .removeEqualMessages(
-                                EVENT_DISCONNECT_REQUESTED,
-                                new EventDisconnectRequestedInfo(
-                                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+                setDisconnectRequestAlarm();
+            } else {
+                // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
+                // and cancel any queued EVENT_DISCONNECT_REQUEST messages
+                cancelDisconnectRequestAlarm();
             }
 
-            sendMessage(
+            sendMessageAndAcquireWakeLock(
                     EVENT_UNDERLYING_NETWORK_CHANGED,
                     TOKEN_ALL,
                     new EventUnderlyingNetworkChangedInfo(underlying));
         }
     }
 
-    private void sendMessage(int what, int token, EventInfo data) {
+    private void acquireWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (mIsRunning) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mWakeLock.release();
+    }
+
+    /**
+     * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
+     * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
+     * no more messags left to process in the Handler queue (at which point the WakeLock can be
+     * released until more messages must be processed).
+     */
+    private void maybeReleaseWakeLock() {
+        final Handler handler = getHandler();
+        if (handler == null || !handler.hasMessagesOrCallbacks()) {
+            releaseWakeLock();
+        }
+    }
+
+    @Override
+    public void sendMessage(int what) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what);
+    }
+
+    @Override
+    public void sendMessage(int what, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, obj);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2, obj);
+    }
+
+    @Override
+    public void sendMessage(Message msg) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(msg);
+    }
+
+    // TODO(b/180146061): also override and Log.wtf() other Message handling methods
+    // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
+    // removeDeferredMessages
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token) {
+        acquireWakeLock();
+        super.sendMessage(what, token);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, ARG_NOT_PRESENT, data);
     }
 
-    private void sendMessage(int what, int token, int arg2, EventInfo data) {
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, arg2, data);
     }
 
-    private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout);
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(Message msg) {
+        acquireWakeLock();
+        super.sendMessage(msg);
     }
 
-    private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, arg2, data, timeout);
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     */
+    private void removeEqualMessages(int what) {
+        removeEqualMessages(what, null /* obj */);
+    }
+
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
+     *     should be removed
+     */
+    private void removeEqualMessages(int what, @Nullable Object obj) {
+        final Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeEqualMessages(what, obj);
+        }
+
+        maybeReleaseWakeLock();
+    }
+
+    private WakeupMessage createScheduledAlarm(
+            @NonNull String cmdName, Message delayedMessage, long delay) {
+        // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
+        // at the scheduled time. dispatchMessage() immediately executes and there may be queued
+        // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
+        // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
+        // guarantees the device will stay awake).
+        final WakeupMessage alarm =
+                mDeps.newWakeupMessage(
+                        mVcnContext,
+                        getHandler(),
+                        cmdName,
+                        () -> sendMessageAndAcquireWakeLock(delayedMessage));
+        alarm.schedule(mDeps.getElapsedRealTime() + delay);
+        return alarm;
+    }
+
+    private void setTeardownTimeoutAlarm() {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mTeardownTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
+        mTeardownTimeoutAlarm =
+                createScheduledAlarm(
+                        TEARDOWN_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+    }
+
+    private void cancelTeardownTimeoutAlarm() {
+        if (mTeardownTimeoutAlarm != null) {
+            mTeardownTimeoutAlarm.cancel();
+            mTeardownTimeoutAlarm = null;
+        }
+
+        // Cancel any existing teardown timeouts
+        removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
+    }
+
+    private void setDisconnectRequestAlarm() {
+        // Only schedule a NEW alarm if none is already set.
+        if (mDisconnectRequestAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage =
+                obtainMessage(
+                        EVENT_DISCONNECT_REQUESTED,
+                        TOKEN_ALL,
+                        0 /* arg2 */,
+                        new EventDisconnectRequestedInfo(
+                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+        mDisconnectRequestAlarm =
+                createScheduledAlarm(
+                        DISCONNECT_REQUEST_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
+    }
+
+    private void cancelDisconnectRequestAlarm() {
+        if (mDisconnectRequestAlarm != null) {
+            mDisconnectRequestAlarm.cancel();
+            mDisconnectRequestAlarm = null;
+        }
+
+        // Cancel any existing disconnect due to previous loss of underlying network
+        removeEqualMessages(
+                EVENT_DISCONNECT_REQUESTED,
+                new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+    }
+
+    private void setRetryTimeoutAlarm(long delay) {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mRetryTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
+        mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
+    }
+
+    private void cancelRetryTimeoutAlarm() {
+        if (mRetryTimeoutAlarm != null) {
+            mRetryTimeoutAlarm.cancel();
+            mRetryTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
     }
 
     private void sessionLost(int token, @Nullable Exception exception) {
-        sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
     }
 
     private void sessionClosed(int token, @Nullable Exception exception) {
         // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
         // Disconnecting state.
         sessionLost(token, exception);
-        sendMessage(EVENT_SESSION_CLOSED, token);
+        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
     }
 
     private void childTransformCreated(
             int token, @NonNull IpSecTransform transform, int direction) {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_TRANSFORM_CREATED,
                 token,
                 new EventTransformCreatedInfo(direction, transform));
     }
 
     private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
-        sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
     }
 
     private abstract class BaseState extends State {
@@ -671,7 +931,7 @@
                 enterState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -682,22 +942,47 @@
         protected void enterState() throws Exception {}
 
         /**
+         * Returns whether the given token is valid.
+         *
+         * <p>By default, States consider any and all token to be 'valid'.
+         *
+         * <p>States should override this method if they want to restrict message handling to
+         * specific tokens.
+         */
+        protected boolean isValidToken(int token) {
+            return true;
+        }
+
+        /**
          * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
          * builds.
+         *
+         * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
+         * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
+         * ensure that mWakeLock is correctly released.
          */
         @Override
-        public boolean processMessage(Message msg) {
+        public final boolean processMessage(Message msg) {
+            final int token = msg.arg1;
+            if (!isValidToken(token)) {
+                Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
+                return HANDLED;
+            }
+
             try {
                 processStateMsg(msg);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
                                 DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
             }
 
+            // Attempt to release the WakeLock - only possible if the Handler queue is empty
+            maybeReleaseWakeLock();
+
             return HANDLED;
         }
 
@@ -709,7 +994,7 @@
                 exitState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -813,24 +1098,7 @@
     }
 
     private abstract class ActiveBaseState extends BaseState {
-        /**
-         * Handles all incoming messages, discarding messages for previous networks.
-         *
-         * <p>States that handle mobility events may need to override this method to receive
-         * messages for all underlying networks.
-         */
         @Override
-        public boolean processMessage(Message msg) {
-            final int token = msg.arg1;
-            // Only process if a valid token is presented.
-            if (isValidToken(token)) {
-                return super.processMessage(msg);
-            }
-
-            Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
-            return HANDLED;
-        }
-
         protected boolean isValidToken(int token) {
             return (token == TOKEN_ALL || token == mCurrentToken);
         }
@@ -861,7 +1129,7 @@
         protected void enterState() throws Exception {
             if (mIkeSession == null) {
                 Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
-                sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+                sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
                 return;
             }
 
@@ -873,10 +1141,9 @@
             }
 
             mIkeSession.close();
-            sendMessageDelayed(
-                    EVENT_TEARDOWN_TIMEOUT_EXPIRED,
-                    mCurrentToken,
-                    TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+
+            // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+            setTeardownTimeoutAlarm();
         }
 
         @Override
@@ -927,6 +1194,8 @@
         @Override
         protected void exitState() throws Exception {
             mSkipRetryTimeout = false;
+
+            cancelTeardownTimeoutAlarm();
         }
     }
 
@@ -1117,6 +1386,18 @@
     class ConnectedState extends ConnectedStateBase {
         @Override
         protected void enterState() throws Exception {
+            if (mTunnelIface == null) {
+                try {
+                    // Requires a real Network object in order to be created; doing this any earlier
+                    // means not having a real Network object, or picking an incorrect Network.
+                    mTunnelIface =
+                            mIpSecManager.createIpSecTunnelInterface(
+                                    DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
+                } catch (IOException | ResourceUnavailableException e) {
+                    teardownAsynchronously();
+                }
+            }
+
             // Successful connection, clear failed attempt counter
             mFailedAttempts = 0;
         }
@@ -1216,8 +1497,8 @@
                 Slog.wtf(TAG, "Underlying network was null in retry state");
                 transitionTo(mDisconnectedState);
             } else {
-                sendMessageDelayed(
-                        EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs());
+                // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+                setRetryTimeoutAlarm(getNextRetryIntervalsMs());
             }
         }
 
@@ -1230,8 +1511,6 @@
 
                     // If new underlying is null, all networks were lost; go back to disconnected.
                     if (mUnderlying == null) {
-                        removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
                         transitionTo(mDisconnectedState);
                         return;
                     } else if (oldUnderlying != null
@@ -1242,8 +1521,6 @@
 
                     // Fallthrough
                 case EVENT_RETRY_TIMEOUT_EXPIRED:
-                    removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
                     transitionTo(mConnectingState);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
@@ -1255,6 +1532,11 @@
             }
         }
 
+        @Override
+        public void exitState() {
+            cancelRetryTimeoutAlarm();
+        }
+
         private long getNextRetryIntervalsMs() {
             final int retryDelayIndex = mFailedAttempts - 1;
             final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs();
@@ -1434,6 +1716,11 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
+        mTunnelIface = tunnelIface;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
     UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
         return mUnderlyingNetworkTrackerCallback;
     }
@@ -1522,6 +1809,26 @@
                     ikeSessionCallback,
                     childSessionCallback);
         }
+
+        /** Builds a new WakeLock. */
+        public VcnWakeLock newWakeLock(
+                @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
+            return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
+        }
+
+        /** Builds a new WakeupMessage. */
+        public WakeupMessage newWakeupMessage(
+                @NonNull VcnContext vcnContext,
+                @NonNull Handler handler,
+                @NonNull String tag,
+                @NonNull Runnable runnable) {
+            return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
+        }
+
+        /** Gets the elapsed real time since boot, in millis. */
+        public long getElapsedRealTime() {
+            return SystemClock.elapsedRealtime();
+        }
     }
 
     /**
@@ -1601,4 +1908,34 @@
             mImpl.setNetwork(network);
         }
     }
+
+    /** Proxy Implementation of WakeLock, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnWakeLock {
+        private final WakeLock mImpl;
+
+        public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) {
+            final PowerManager powerManager = context.getSystemService(PowerManager.class);
+            mImpl = powerManager.newWakeLock(flags, tag);
+            mImpl.setReferenceCounted(false /* isReferenceCounted */);
+        }
+
+        /**
+         * Acquire this WakeLock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void acquire() {
+            mImpl.acquire();
+        }
+
+        /**
+         * Release this Wakelock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void release() {
+            mImpl.release();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 3968723..685dce4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -21,16 +21,14 @@
 import android.os.CombinedVibrationEffect;
 import android.os.Handler;
 import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorManager;
 import android.util.SparseArray;
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
 
-/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
+/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
+final class InputDeviceDelegate implements InputManager.InputDeviceListener {
     private static final String TAG = "InputDeviceDelegate";
 
     private final Object mLock = new Object();
@@ -38,7 +36,7 @@
     private final InputManager mInputManager;
 
     @GuardedBy("mLock")
-    private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();
+    private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>();
 
     /**
      * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
@@ -47,7 +45,7 @@
     @GuardedBy("mLock")
     private boolean mShouldVibrateInputDevices;
 
-    public InputDeviceDelegate(Context context, Handler handler) {
+    InputDeviceDelegate(Context context, Handler handler) {
         mHandler = handler;
         mInputManager = context.getSystemService(InputManager.class);
     }
@@ -81,32 +79,22 @@
     }
 
     /**
-     * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
+     * Vibrate all {@link InputDevice} with vibrators using given effect.
      *
      * @return {@link #isAvailable()}
      */
     public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibrationEffect effect,
             String reason, VibrationAttributes attrs) {
         synchronized (mLock) {
-            // TODO(b/159207608): Pass on the combined vibration once InputManager is merged
-            if (effect instanceof CombinedVibrationEffect.Mono) {
-                VibrationEffect e = ((CombinedVibrationEffect.Mono) effect).getEffect();
-                if (e instanceof VibrationEffect.Prebaked) {
-                    VibrationEffect fallback = ((VibrationEffect.Prebaked) e).getFallbackEffect();
-                    if (fallback != null) {
-                        e = fallback;
-                    }
-                }
-                for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
-                    mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, e, reason, attrs);
-                }
+            for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
+                mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
             }
             return mInputDeviceVibrators.size() > 0;
         }
     }
 
     /**
-     * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
+     * Cancel vibration on all {@link InputDevice} with vibrators.
      *
      * @return {@link #isAvailable()}
      */
@@ -147,9 +135,9 @@
                     if (device == null) {
                         continue;
                     }
-                    Vibrator vibrator = device.getVibrator();
-                    if (vibrator.hasVibrator()) {
-                        mInputDeviceVibrators.put(device.getId(), vibrator);
+                    VibratorManager vibratorManager = device.getVibratorManager();
+                    if (vibratorManager.getVibratorIds().length > 0) {
+                        mInputDeviceVibrators.put(device.getId(), vibratorManager);
                     }
                 }
             } else {
@@ -171,9 +159,9 @@
                 mInputDeviceVibrators.remove(deviceId);
                 return;
             }
-            Vibrator vibrator = device.getVibrator();
-            if (vibrator.hasVibrator()) {
-                mInputDeviceVibrators.put(deviceId, vibrator);
+            VibratorManager vibratorManager = device.getVibratorManager();
+            if (vibratorManager.getVibratorIds().length > 0) {
+                mInputDeviceVibrators.put(device.getId(), vibratorManager);
             } else {
                 mInputDeviceVibrators.remove(deviceId);
             }
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e0f5408..607da3c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,25 +25,16 @@
 import android.os.VibrationEffect;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.ComposedProto;
-import com.android.server.OneShotProto;
-import com.android.server.PrebakedProto;
-import com.android.server.VibrationAttributesProto;
-import com.android.server.VibrationEffectProto;
-import com.android.server.VibrationProto;
-import com.android.server.WaveformProto;
-
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 /** Represents a vibration request to the vibrator service. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public class Vibration {
+final class Vibration {
     private static final String TAG = "Vibration";
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
-    public enum Status {
+    enum Status {
         RUNNING,
         FINISHED,
         FORWARDED_TO_INPUT_DEVICES,
@@ -91,7 +82,7 @@
     private long mEndTimeDebug;
     private Status mStatus;
 
-    public Vibration(IBinder token, int id, CombinedVibrationEffect effect,
+    Vibration(IBinder token, int id, CombinedVibrationEffect effect,
             VibrationAttributes attrs, int uid, String opPkg, String reason) {
         this.token = token;
         this.mEffect = effect;
@@ -157,7 +148,7 @@
     }
 
     /** Debug information about vibrations. */
-    public static final class DebugInfo {
+    static final class DebugInfo {
         private final long mStartTimeDebug;
         private final long mEndTimeDebug;
         private final CombinedVibrationEffect mEffect;
@@ -169,7 +160,7 @@
         private final String mReason;
         private final Status mStatus;
 
-        public DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
+        DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
                 CombinedVibrationEffect originalEffect, float scale, VibrationAttributes attrs,
                 int uid, String opPkg, String reason, Status status) {
             mStartTimeDebug = startTimeDebug;
@@ -235,21 +226,49 @@
         }
 
         private void dumpEffect(
-                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect combinedEffect) {
-            VibrationEffect effect;
-            // TODO(b/177805090): add proper support for dumping combined effects to proto
-            if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
-                effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
-            } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
-                effect = ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects().valueAt(0);
-            } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
-                dumpEffect(proto, fieldId,
-                        ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects().get(0));
-                return;
-            } else {
-                // Unknown combined effect, skip dump.
-                return;
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect effect) {
+            dumpEffect(proto, fieldId,
+                    (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+                            .addNext(effect)
+                            .combine());
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Sequential effect) {
+            final long token = proto.start(fieldId);
+            for (int i = 0; i < effect.getEffects().size(); i++) {
+                CombinedVibrationEffect nestedEffect = effect.getEffects().get(i);
+                if (nestedEffect instanceof CombinedVibrationEffect.Mono) {
+                    dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+                            (CombinedVibrationEffect.Mono) nestedEffect);
+                } else if (nestedEffect instanceof CombinedVibrationEffect.Stereo) {
+                    dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+                            (CombinedVibrationEffect.Stereo) nestedEffect);
+                }
+                proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
             }
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Mono effect) {
+            final long token = proto.start(fieldId);
+            dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Stereo effect) {
+            final long token = proto.start(fieldId);
+            for (int i = 0; i < effect.getEffects().size(); i++) {
+                proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
+                dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
+            }
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
             final long token = proto.start(fieldId);
             if (effect instanceof VibrationEffect.OneShot) {
                 dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0fa4fe1..10393f6 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -29,8 +29,7 @@
 import java.util.Objects;
 
 /** Controls vibration scaling. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationScaler {
+final class VibrationScaler {
     private static final String TAG = "VibrationScaler";
 
     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
@@ -56,7 +55,7 @@
     private final VibrationSettings mSettingsController;
     private final int mDefaultVibrationAmplitude;
 
-    public VibrationScaler(Context context, VibrationSettings settingsController) {
+    VibrationScaler(Context context, VibrationSettings settingsController) {
         mSettingsController = settingsController;
         mDefaultVibrationAmplitude = context.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultVibrationAmplitude);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8910bdf..334129d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -39,20 +39,18 @@
 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;
 
 /** Controls all the system settings related to vibration. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationSettings {
+final class VibrationSettings {
     private static final String TAG = "VibrationSettings";
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
 
     /** Listener for changes on vibration settings. */
-    public interface OnVibratorSettingsChanged {
+    interface OnVibratorSettingsChanged {
         /** Callback triggered when any of the vibrator settings change. */
         void onChange();
     }
@@ -86,7 +84,7 @@
     @GuardedBy("mLock")
     private boolean mLowPowerMode;
 
-    public VibrationSettings(Context context, Handler handler) {
+    VibrationSettings(Context context, Handler handler) {
         mContext = context;
         mVibrator = context.getSystemService(Vibrator.class);
         mAudioManager = context.getSystemService(AudioManager.class);
@@ -345,17 +343,17 @@
     /** Write current settings into given {@link ProtoOutputStream}. */
     public void dumpProto(ProtoOutputStream proto) {
         synchronized (mLock) {
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
                     mHapticFeedbackIntensity);
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
                     mVibrator.getDefaultHapticFeedbackIntensity());
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
                     mNotificationIntensity);
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
                     mVibrator.getDefaultNotificationVibrationIntensity());
-            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
                     mRingIntensity);
-            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
                     mVibrator.getDefaultRingVibrationIntensity());
         }
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 04dac7c..3893267 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -42,8 +42,7 @@
 import java.util.PriorityQueue;
 
 /** Plays a {@link Vibration} in dedicated thread. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationThread extends Thread implements IBinder.DeathRecipient {
+final class VibrationThread extends Thread implements IBinder.DeathRecipient {
     private static final String TAG = "VibrationThread";
     private static final boolean DEBUG = false;
 
@@ -54,7 +53,7 @@
     private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
 
     /** Callbacks for playing a {@link Vibration}. */
-    public interface VibrationCallbacks {
+    interface VibrationCallbacks {
 
         /**
          * Callback triggered before starting a synchronized vibration step. This will be called
@@ -92,7 +91,7 @@
     @GuardedBy("mLock")
     private boolean mForceStop;
 
-    public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
+    VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
             VibrationCallbacks callbacks) {
         mVibration = vib;
@@ -169,7 +168,7 @@
         }
     }
 
-    public Vibration getVibration() {
+    Vibration getVibration() {
         return mVibration;
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 9dcf12c..95f6059 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -32,8 +32,7 @@
 import libcore.util.NativeAllocationRegistry;
 
 /** Controls a single vibrator. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibratorController {
+final class VibratorController {
     private static final String TAG = "VibratorController";
 
     private final Object mLock = new Object();
@@ -99,12 +98,12 @@
 
     static native void vibratorAlwaysOnDisable(long nativePtr, long id);
 
-    public VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
+    VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
         this(vibratorId, listener, new NativeWrapper());
     }
 
     @VisibleForTesting
-    public VibratorController(int vibratorId, OnVibrationCompleteListener listener,
+    VibratorController(int vibratorId, OnVibrationCompleteListener listener,
             NativeWrapper nativeWrapper) {
         mNativeWrapper = nativeWrapper;
         mNativeWrapper.init(vibratorId, listener);
@@ -142,11 +141,6 @@
         }
     }
 
-    @VisibleForTesting
-    public NativeWrapper getNativeWrapper() {
-        return mNativeWrapper;
-    }
-
     /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
     public VibratorInfo getVibratorInfo() {
         return mVibratorInfo;
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
similarity index 96%
rename from services/core/java/com/android/server/VibratorManagerService.java
rename to services/core/java/com/android/server/vibrator/VibratorManagerService.java
index d264f85..1750854 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.vibrator;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -56,12 +56,8 @@
 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 com.android.server.LocalServices;
+import com.android.server.SystemService;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -356,10 +352,7 @@
                     return;
                 }
 
-                VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks);
-
-                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
                 if (ignoreStatus != null) {
                     endVibrationLocked(vib, ignoreStatus);
                     return;
@@ -370,7 +363,7 @@
                     if (mCurrentVibration != null) {
                         mCurrentVibration.cancel();
                     }
-                    Vibration.Status status = startVibrationLocked(vibThread);
+                    Vibration.Status status = startVibrationLocked(vib);
                     if (status != Vibration.Status.RUNNING) {
                         endVibrationLocked(vib, status);
                     }
@@ -495,19 +488,19 @@
     }
 
     @GuardedBy("mLock")
-    private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+    private Vibration.Status startVibrationLocked(Vibration vib) {
         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;
             }
 
+            VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+                    mBatteryStatsService, mVibrationCallbacks);
+
             if (mCurrentVibration == null) {
                 return startVibrationThreadLocked(vibThread);
             }
@@ -599,8 +592,8 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
-        if (vibThread.getVibration().isRepeating()) {
+    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) {
+        if (vibration.isRepeating()) {
             // Repeating vibrations always take precedence.
             return null;
         }
@@ -967,7 +960,7 @@
 
     /** Listener for synced vibration completion callbacks from native. */
     @VisibleForTesting
-    public interface OnSyncedVibrationCompleteListener {
+    interface OnSyncedVibrationCompleteListener {
 
         /** Callback triggered when synced vibration is complete. */
         void onComplete(long vibrationId);
@@ -1167,6 +1160,7 @@
                     }
                 }
 
+                pw.println();
                 pw.println("  Previous external vibrations:");
                 for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
                     pw.println("    " + info);
@@ -1181,46 +1175,48 @@
                 mVibrationSettings.dumpProto(proto);
                 if (mCurrentVibration != null) {
                     mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
-                            VibratorServiceDumpProto.CURRENT_VIBRATION);
+                            VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
                 }
                 if (mCurrentExternalVibration != null) {
                     mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                            VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+                            VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
                 }
 
                 boolean isVibrating = false;
                 boolean isUnderExternalControl = false;
                 for (int i = 0; i < mVibrators.size(); i++) {
+                    proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
                     isVibrating |= mVibrators.valueAt(i).isVibrating();
                     isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
                 }
-                proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
-                proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+                proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+                proto.write(VibratorManagerServiceDumpProto.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 (int i = 0; i < mPreviousVibrations.size(); i++) {
+                    long fieldId;
+                    switch (mPreviousVibrations.keyAt(i)) {
+                        case VibrationAttributes.USAGE_RINGTONE:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+                            break;
+                        case VibrationAttributes.USAGE_NOTIFICATION:
+                            fieldId = VibratorManagerServiceDumpProto
+                                    .PREVIOUS_NOTIFICATION_VIBRATIONS;
+                            break;
+                        case VibrationAttributes.USAGE_ALARM:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+                            break;
+                        default:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
+                    }
+                    for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+                        info.dumpProto(proto, fieldId);
+                    }
                 }
 
                 for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                    info.dumpProto(proto,
+                            VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
                 }
             }
             proto.flush();
@@ -1300,7 +1296,7 @@
                     cancelingVibration.join();
                 } catch (InterruptedException e) {
                     Slog.w("Interrupted while waiting for vibration to finish before starting "
-                                    + "external control", e);
+                            + "external control", e);
                 }
             }
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 771b712..c63a0f0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -134,10 +134,10 @@
     }
 
     @Override
-    public void activityResumed(IBinder token) {
+    public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
-            ActivityRecord.activityResumedLocked(token);
+            ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -692,6 +692,18 @@
     }
 
     /**
+     * Splash screen view is attached to activity.
+     */
+    @Override
+    public void splashScreenAttached(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized (mGlobalLock) {
+            ActivityRecord.splashScreenAttachedLocked(token);
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 68a2c5d..9bf6df4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -42,6 +42,8 @@
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -243,6 +245,7 @@
 import android.app.servertransaction.StartActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.app.servertransaction.TopResumedActivityChangeItem;
+import android.app.servertransaction.TransferSplashScreenViewStateItem;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -300,6 +303,7 @@
 import android.view.WindowManager.TransitionOldType;
 import android.view.animation.Animation;
 import android.window.IRemoteTransition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -669,6 +673,30 @@
     boolean startingDisplayed;
     boolean startingMoved;
 
+    boolean mHandleExitSplashScreen;
+    @TransferSplashScreenState int mTransferringSplashScreenState =
+            TRANSFER_SPLASH_SCREEN_IDLE;
+
+    // Idle, can be triggered to do transfer if needed.
+    static final int TRANSFER_SPLASH_SCREEN_IDLE = 0;
+    // requesting a copy from shell.
+    static final int TRANSFER_SPLASH_SCREEN_COPYING = 1;
+    // attach the splash screen view to activity.
+    static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2;
+    // client has taken over splash screen view.
+    static final int TRANSFER_SPLASH_SCREEN_FINISH = 3;
+
+    @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = {
+            TRANSFER_SPLASH_SCREEN_IDLE,
+            TRANSFER_SPLASH_SCREEN_COPYING,
+            TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT,
+            TRANSFER_SPLASH_SCREEN_FINISH,
+    })
+    @interface TransferSplashScreenState {}
+
+    // How long we wait until giving up transfer splash screen.
+    private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
+
     // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
     boolean mIsExiting;
     // Force an app transition to be ran in the case the visibility of the app did not change.
@@ -1727,6 +1755,7 @@
         if (_createTime > 0) {
             createTime = _createTime;
         }
+        mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
     }
 
     /**
@@ -1807,7 +1836,85 @@
         return hasProcess() && app.hasThread();
     }
 
-    boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
+    /**
+     * Evaluate the theme for a starting window.
+     * @param originalTheme The original theme which read from activity or application.
+     * @param replaceTheme The replace theme which requested from starter.
+     * @return Resolved theme.
+     */
+    private int evaluateStartingWindowTheme(String pkg, int originalTheme, int replaceTheme) {
+        // Skip if the package doesn't want a starting window.
+        if (!validateStartingWindowTheme(pkg, originalTheme)) {
+            return 0;
+        }
+        int selectedTheme = originalTheme;
+        if (replaceTheme != 0 && validateStartingWindowTheme(pkg, replaceTheme)) {
+            // allow to replace theme
+            selectedTheme = replaceTheme;
+        }
+        return selectedTheme;
+    }
+
+    private boolean validateStartingWindowTheme(String pkg, int theme) {
+        // If this is a translucent window, then don't show a starting window -- the current
+        // effect (a full-screen opaque starting window that fades away to the real contents
+        // when it is ready) does not work for this.
+        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
+        if (theme != 0) {
+            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                    com.android.internal.R.styleable.Window,
+                    mWmService.mCurrentUserId);
+            if (ent == null) {
+                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
+                // see that.
+                return false;
+            }
+            final boolean windowIsTranslucent = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+            final boolean windowIsFloating = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsFloating, false);
+            final boolean windowShowWallpaper = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+            final boolean windowDisableStarting = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
+            ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
+                    "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+                    windowIsTranslucent, windowIsFloating, windowShowWallpaper,
+                    windowDisableStarting);
+            if (windowIsTranslucent || windowIsFloating || windowDisableStarting) {
+                return false;
+            }
+            if (windowShowWallpaper
+                    && getDisplayContent().mWallpaperController.getWallpaperTarget() != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void applyStartingWindowTheme(String pkg, int theme) {
+        if (theme != 0) {
+            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                    com.android.internal.R.styleable.Window,
+                    mWmService.mCurrentUserId);
+            if (ent == null) {
+                return;
+            }
+            final boolean windowShowWallpaper = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+            if (windowShowWallpaper && getDisplayContent().mWallpaperController
+                    .getWallpaperTarget() == null) {
+                // If this theme is requesting a wallpaper, and the wallpaper
+                // is not currently visible, then this effectively serves as
+                // an opaque window and our starting window transition animation
+                // can still work.  We just need to make sure the starting window
+                // is also showing the wallpaper.
+                windowFlags |= FLAG_SHOW_WALLPAPER;
+            }
+        }
+    }
+
+    boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
             CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
             IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
             boolean allowTaskSnapshot, boolean activityCreated) {
@@ -1850,49 +1957,12 @@
             return createSnapshot(snapshot, typeParameter);
         }
 
-        // If this is a translucent window, then don't show a starting window -- the current
-        // effect (a full-screen opaque starting window that fades away to the real contents
-        // when it is ready) does not work for this.
-        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
-        if (theme != 0) {
-            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
-                    com.android.internal.R.styleable.Window,
-                    mWmService.mCurrentUserId);
-            if (ent == null) {
-                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
-                // see that.
-                return false;
-            }
-            final boolean windowIsTranslucent = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
-            final boolean windowIsFloating = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowIsFloating, false);
-            final boolean windowShowWallpaper = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
-            final boolean windowDisableStarting = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
-                    windowIsTranslucent, windowIsFloating, windowShowWallpaper);
-            if (windowIsTranslucent) {
-                return false;
-            }
-            if (windowIsFloating || windowDisableStarting) {
-                return false;
-            }
-            if (windowShowWallpaper) {
-                if (getDisplayContent().mWallpaperController
-                        .getWallpaperTarget() == null) {
-                    // If this theme is requesting a wallpaper, and the wallpaper
-                    // is not currently visible, then this effectively serves as
-                    // an opaque window and our starting window transition animation
-                    // can still work.  We just need to make sure the starting window
-                    // is also showing the wallpaper.
-                    windowFlags |= FLAG_SHOW_WALLPAPER;
-                } else {
-                    return false;
-                }
-            }
+        // Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0
+        // but original theme is not 0, means this package doesn't want a starting window.
+        if (resolvedTheme == 0 && theme != 0) {
+            return false;
         }
+        applyStartingWindowTheme(pkg, resolvedTheme);
 
         if (transferStartingWindow(transferFrom)) {
             return true;
@@ -1906,7 +1976,7 @@
 
         ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
         mStartingData = new SplashScreenStartingData(mWmService, pkg,
-                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+                resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                 getMergedOverrideConfiguration(), typeParameter);
         scheduleAddStartingWindow();
         return true;
@@ -2031,7 +2101,118 @@
         return snapshot.getRotation() == targetRotation;
     }
 
+    /**
+     * See {@link SplashScreen#setOnExitAnimationListener}.
+     */
+    void setCustomizeSplashScreenExitAnimation(boolean enable) {
+        if (mHandleExitSplashScreen == enable) {
+            return;
+        }
+        mHandleExitSplashScreen = enable;
+    }
+
+    private final Runnable mTransferSplashScreenTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mAtmService.mGlobalLock) {
+                Slog.w(TAG, "Activity transferring splash screen timeout for "
+                        + ActivityRecord.this + " state " + mTransferringSplashScreenState);
+                if (isTransferringSplashScreen()) {
+                    mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+                    // TODO show default exit splash screen animation
+                    removeStartingWindow();
+                }
+            }
+        }
+    };
+
+    private void scheduleTransferSplashScreenTimeout() {
+        mAtmService.mH.postDelayed(mTransferSplashScreenTimeoutRunnable,
+                TRANSFER_SPLASH_SCREEN_TIMEOUT);
+    }
+
+    private void removeTransferSplashScreenTimeout() {
+        mAtmService.mH.removeCallbacks(mTransferSplashScreenTimeoutRunnable);
+    }
+
+    private boolean transferSplashScreenIfNeeded() {
+        if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+            return false;
+        }
+        if (isTransferringSplashScreen()) {
+            return true;
+        }
+        requestCopySplashScreen();
+        return isTransferringSplashScreen();
+    }
+
+    private boolean isTransferringSplashScreen() {
+        return mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+    }
+
+    private void requestCopySplashScreen() {
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
+        if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+            removeStartingWindow();
+        }
+        scheduleTransferSplashScreenTimeout();
+    }
+
+    /**
+     * Receive the splash screen data from shell, sending to client.
+     * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy.
+     */
+    void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
+        removeTransferSplashScreenTimeout();
+        // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
+        // either way, abort and reset the sequence.
+        if (parcelable == null
+                || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+            if (parcelable != null) {
+                parcelable.clearIfNeeded();
+            }
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+            removeStartingWindow();
+            return;
+        }
+        // schedule attach splashScreen to client
+        try {
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                    TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+            scheduleTransferSplashScreenTimeout();
+        } catch (Exception e) {
+            Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+            parcelable.clearIfNeeded();
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+        }
+    }
+
+    private void onSplashScreenAttachComplete() {
+        removeTransferSplashScreenTimeout();
+        // Client has draw the splash screen, so we can remove the starting window.
+        if (mStartingWindow != null) {
+            mStartingWindow.hide(false, false);
+        }
+        try {
+            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                    TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
+        } catch (Exception e) {
+            Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
+        }
+        // no matter what, remove the starting window.
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+        removeStartingWindow();
+    }
+
     void removeStartingWindow() {
+        if (transferSplashScreenIfNeeded()) {
+            return;
+        }
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
         if (mStartingWindow == null) {
             if (mStartingData != null) {
                 // Starting window has not been added yet, but it is scheduled to be added.
@@ -5092,7 +5273,7 @@
         }
     }
 
-    static void activityResumedLocked(IBinder token) {
+    static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r);
         if (r == null) {
@@ -5100,12 +5281,22 @@
             // been removed (e.g. destroy timeout), so the token could be null.
             return;
         }
+        r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit);
         r.setSavedState(null /* savedState */);
 
         r.mDisplayContent.handleActivitySizeCompatModeIfNeeded(r);
         r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
     }
 
+    static void splashScreenAttachedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        if (r == null) {
+            Slog.w(TAG, "splashScreenTransferredLocked cannot find activity");
+            return;
+        }
+        r.onSplashScreenAttachComplete();
+    }
+
     /**
      * Once we know that we have asked an application to put an activity in the resumed state
      * (either by launching it or explicitly telling it), this function updates the rest of our
@@ -5187,6 +5378,7 @@
             }
         }
 
+        mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
         mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
@@ -5931,7 +6123,13 @@
         pendingVoiceInteractionStart = false;
     }
 
-    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+    void showStartingWindow(boolean taskSwitch) {
+        showStartingWindow(null /* prev */, false /* newTask */, taskSwitch,
+                0 /* splashScreenTheme */);
+    }
+
+    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+            int splashScreenTheme) {
         if (mTaskOverlay) {
             // We don't show starting window for overlay activities.
             return;
@@ -5944,7 +6142,10 @@
 
         final CompatibilityInfo compatInfo =
                 mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
-        final boolean shown = addStartingWindow(packageName, theme,
+
+        final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme,
+                splashScreenTheme);
+        final boolean shown = addStartingWindow(packageName, resolvedTheme,
                 compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                 prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                 allowTaskSnapshot(),
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 79f8229..37fda4c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1997,8 +1997,7 @@
 
         if (mMovedToFront) {
             // We moved the task to front, use starting window to hide initial drawn delay.
-            targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */,
-                    true /* taskSwitch */);
+            targetTaskTop.showStartingWindow(true /* taskSwitch */);
         } else if (mDoResume) {
             // Make sure the root task and its belonging display are moved to topmost.
             mTargetRootTask.moveToFront("intentActivityFound");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7d2075c..94379b1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -574,4 +574,26 @@
      * @return Whether the package is the base of any locked task
      */
     public abstract boolean isBaseOfLockedTask(String packageName);
+
+    /**
+     * Create an interface to update configuration for an application.
+     */
+    public abstract PackageConfigurationUpdater createPackageConfigurationUpdater();
+
+    /**
+     * An interface to update configuration for an application, and will persist override
+     * configuration for this package.
+     */
+    public interface PackageConfigurationUpdater {
+        /**
+         * Sets the dark mode for the current application. This setting is persisted and will
+         * override the system configuration for this application.
+         */
+        PackageConfigurationUpdater setNightMode(int nightMode);
+
+        /**
+         * Commit changes.
+         */
+        void commit() throws RemoteException;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f16a646..2e98c2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -224,6 +224,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerTransaction;
 
@@ -451,6 +452,7 @@
     /** The controller for all operations related to locktask. */
     private LockTaskController mLockTaskController;
     private ActivityStartController mActivityStartController;
+    PackageConfigPersister mPackageConfigPersister;
 
     boolean mSuppressResizeConfigChanges;
 
@@ -866,6 +868,7 @@
         setRecentTasks(new RecentTasks(this, mTaskSupervisor));
         mVrController = new VrController(mGlobalLock);
         mKeyguardController = mTaskSupervisor.getKeyguardController();
+        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
     }
 
     public void onActivityManagerInternalAdded() {
@@ -2027,8 +2030,7 @@
 
                 // We are reshowing a task, use a starting window to hide the initial draw delay
                 // so the transition can start earlier.
-                topActivity.showStartingWindow(null /* prev */, false /* newTask */,
-                        true /* taskSwitch */);
+                topActivity.showStartingWindow(true /* taskSwitch */);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3249,6 +3251,30 @@
     }
 
     /**
+     * A splash screen view has copied, pass it to an activity.
+     *
+     * @param taskId Id of task to handle the material to reconstruct the view.
+     * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+     * @hide
+     */
+    @Override
+    public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
+            throws RemoteException {
+        mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
+                "copySplashScreenViewFinish()");
+        synchronized (mGlobalLock) {
+            final Task task = mRootWindowContainer.anyTaskForId(taskId,
+                    MATCH_ATTACHED_TASK_ONLY);
+            if (task != null) {
+                final ActivityRecord r = task.getTopWaitSplashScreenActivity();
+                if (r != null) {
+                    r.onCopySplashScreenFinish(parcelable);
+                }
+            }
+        }
+    }
+
+    /**
      * Puts the given activity in picture in picture mode if possible.
      *
      * @return true if the activity is now in picture-in-picture mode, or false if it could not
@@ -5433,6 +5459,7 @@
             synchronized (mGlobalLock) {
                 mAppWarnings.onPackageUninstalled(name);
                 mCompatModePackages.handlePackageUninstalledLocked(name);
+                mPackageConfigPersister.onPackageUninstall(name);
             }
         }
 
@@ -6103,6 +6130,7 @@
         public void removeUser(int userId) {
             synchronized (mGlobalLock) {
                 mRootWindowContainer.removeUser(userId);
+                mPackageConfigPersister.removeUser(userId);
             }
         }
 
@@ -6200,6 +6228,8 @@
         public void loadRecentTasksForUser(int userId) {
             synchronized (mGlobalLock) {
                 mRecentTasks.loadUserRecentsLocked(userId);
+                // TODO renaming the methods(?)
+                mPackageConfigPersister.loadUserPackages(userId);
             }
         }
 
@@ -6308,5 +6338,54 @@
                 return getLockTaskController().isBaseOfLockedTask(packageName);
             }
         }
+
+        @Override
+        public PackageConfigurationUpdater createPackageConfigurationUpdater() {
+            synchronized (mGlobalLock) {
+                return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+            }
+        }
+    }
+
+    final class PackageConfigurationUpdaterImpl implements
+            ActivityTaskManagerInternal.PackageConfigurationUpdater {
+        private int mPid;
+        private int mNightMode;
+
+        PackageConfigurationUpdaterImpl(int pid) {
+            mPid = pid;
+        }
+
+        @Override
+        public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+            mNightMode = nightMode;
+            return this;
+        }
+
+        @Override
+        public void commit() throws RemoteException {
+            if (mPid == 0) {
+                throw new RemoteException("Invalid process");
+            }
+            synchronized (mGlobalLock) {
+                final WindowProcessController wpc = mProcessMap.getProcess(mPid);
+                if (wpc == null) {
+                    Slog.w(TAG, "Override application configuration: cannot find application");
+                    return;
+                }
+                if (wpc.getNightMode() == mNightMode) {
+                    return;
+                }
+                if (!wpc.setOverrideNightMode(mNightMode)) {
+                    return;
+                }
+                wpc.updateNightModeForAllActivities(mNightMode);
+                mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
+            }
+        }
+
+        int getNightMode() {
+            return mNightMode;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d90e885..efcaaa4 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,6 +534,28 @@
         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
     }
 
+    /**
+     * Overrides the night mode applied to this ConfigurationContainer.
+     * @return true if the nightMode has been changed.
+     */
+    public boolean setOverrideNightMode(int nightMode) {
+        final int currentUiMode = mFullConfiguration.uiMode;
+        final int currentNightMode = getNightMode();
+        final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
+        if (currentNightMode == validNightMode) {
+            return false;
+        }
+        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+        mRequestsTmpConfig.uiMode = validNightMode
+                | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        return true;
+    }
+
+    int getNightMode() {
+        return mFullConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+    }
+
     public boolean isActivityTypeDream() {
         return getActivityType() == ACTIVITY_TYPE_DREAM;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f075d85..759b7fe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -429,6 +429,10 @@
 
     void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) {
         if (mOrganizer == organizer) return;
+        if (mDisplayContent == null || !mDisplayContent.isTrusted()) {
+            throw new IllegalStateException(
+                    "Don't organize or trigger events for unavailable or untrusted display.");
+        }
         IDisplayAreaOrganizer lastOrganizer = mOrganizer;
         // Update the new display area organizer before calling sendDisplayAreaVanished since it
         // could result in a new SurfaceControl getting created that would notify the old organizer
@@ -500,6 +504,17 @@
         return false;
     }
 
+    @Override
+    void removeImmediately() {
+        setOrganizer(null);
+        super.removeImmediately();
+    }
+
+    @Override
+    DisplayArea getDisplayArea() {
+        return this;
+    }
+
     /**
      * DisplayArea that contains WindowTokens, and orders them according to their type.
      */
@@ -580,11 +595,6 @@
         }
     }
 
-    @Override
-    DisplayArea getDisplayArea() {
-        return this;
-    }
-
     /**
      * DisplayArea that can be dimmed.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index ed44876..2beb378 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -21,6 +21,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.DisplayArea.Type.ANY;
 
+import android.annotation.Nullable;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
@@ -77,6 +78,11 @@
         mService.enforceTaskPermission(func);
     }
 
+    @Nullable
+    IDisplayAreaOrganizer getOrganizerByFeature(int featureId) {
+        return mOrganizersByFeatureIds.get(featureId);
+    }
+
     @Override
     public ParceledListSlice<DisplayAreaAppearedInfo> registerOrganizer(
             IDisplayAreaOrganizer organizer, int feature) {
@@ -100,10 +106,18 @@
                 }
 
                 final List<DisplayAreaAppearedInfo> displayAreaInfos = new ArrayList<>();
-                mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
-                    if (da.mFeatureId != feature) return;
-                    displayAreaInfos.add(organizeDisplayArea(organizer, da,
-                            "DisplayAreaOrganizerController.registerOrganizer"));
+                mService.mRootWindowContainer.forAllDisplays(dc -> {
+                    if (!dc.isTrusted()) {
+                        ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER,
+                                "Don't organize or trigger events for untrusted displayId=%d",
+                                dc.getDisplayId());
+                        return;
+                    }
+                    dc.forAllDisplayAreas((da) -> {
+                        if (da.mFeatureId != feature) return;
+                        displayAreaInfos.add(organizeDisplayArea(organizer, da,
+                                "DisplayAreaOrganizerController.registerOrganizer"));
+                    });
                 });
 
                 mOrganizersByFeatureIds.put(feature, organizer);
@@ -148,6 +162,10 @@
                     throw new IllegalArgumentException("createTaskDisplayArea unknown displayId="
                             + displayId);
                 }
+                if (!display.isTrusted()) {
+                    throw new IllegalArgumentException("createTaskDisplayArea untrusted displayId="
+                            + displayId);
+                }
 
                 // The parentFeatureId can be either a RootDisplayArea or a TaskDisplayArea.
                 // Check if there is a RootDisplayArea with the given parentFeatureId.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 23eab98..86968ed 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -213,6 +213,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.IDisplayAreaOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -239,6 +240,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -662,12 +664,8 @@
      */
     private boolean mRemoved;
 
-    /**
-     * Non-null if the last size compatibility mode activity is using non-native screen
-     * configuration. The activity is not able to put in multi-window mode, so it exists only one
-     * per display.
-     */
-    private ActivityRecord mLastCompatModeActivity;
+    /** Set of activities in foreground size compat mode. */
+    private Set<ActivityRecord> mActiveSizeCompatActivities = new ArraySet<>();
 
     // Used in updating the display size
     private Point mTmpDisplaySize = new Point();
@@ -1074,6 +1072,7 @@
 
         // Sets the display content for the children.
         onDisplayChanged(this);
+        updateDisplayAreaOrganizers();
 
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
@@ -2712,6 +2711,30 @@
     }
 
     /**
+     * Checks for all non-organized {@link DisplayArea}s for if there is any existing organizer for
+     * their features. If so, registers them with the matched organizer.
+     */
+    @VisibleForTesting
+    void updateDisplayAreaOrganizers() {
+        if (!isTrusted()) {
+            // No need to update for untrusted display.
+            return;
+        }
+        forAllDisplayAreas(displayArea -> {
+            if (displayArea.isOrganized()) {
+                return;
+            }
+            // Check if we have a registered organizer for the DA feature.
+            final IDisplayAreaOrganizer organizer =
+                    mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+                            .getOrganizerByFeature(displayArea.mFeatureId);
+            if (organizer != null) {
+                displayArea.setOrganizer(organizer);
+            }
+        });
+    }
+
+    /**
      * Returns true if the input point is within an app window.
      */
     boolean pointWithinAppWindow(int x, int y) {
@@ -5527,24 +5550,23 @@
     /** Checks whether the given activity is in size compatibility mode and notifies the change. */
     void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
         final Task organizedTask = r.getOrganizedTask();
-        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
-                || organizedTask == null) {
-            // The callback is only interested in the foreground changes of fullscreen activity.
+        if (organizedTask == null) {
+            mActiveSizeCompatActivities.remove(r);
             return;
         }
-        // TODO(b/178327644) Update for per Task size compat
-        if (!r.inSizeCompatMode()) {
-            if (mLastCompatModeActivity != null) {
+
+        if (r.isState(RESUMED) && r.inSizeCompatMode()) {
+            if (mActiveSizeCompatActivities.add(r)) {
+                // Trigger task event for new size compat activity.
                 organizedTask.onSizeCompatActivityChanged();
             }
-            mLastCompatModeActivity = null;
             return;
         }
-        if (mLastCompatModeActivity == r) {
-            return;
+
+        if (mActiveSizeCompatActivities.remove(r)) {
+            // Trigger task event for activity no longer in foreground size compat.
+            organizedTask.onSizeCompatActivityChanged();
         }
-        mLastCompatModeActivity = r;
-        organizedTask.onSizeCompatActivityChanged();
     }
 
     boolean isUidPresent(int uid) {
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
new file mode 100644
index 0000000..1552a96
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * Persist configuration for each package, only persist the change if some on attributes are
+ * different from the global configuration. This class only applies to packages with Activities.
+ */
+public class PackageConfigPersister {
+    private static final String TAG = PackageConfigPersister.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String TAG_CONFIG = "config";
+    private static final String ATTR_PACKAGE_NAME = "package_name";
+    private static final String ATTR_NIGHT_MODE = "night_mode";
+
+    private static final String PACKAGE_DIRNAME = "package_configs";
+    private static final String SUFFIX_FILE_NAME = "_config.xml";
+
+    private final PersisterQueue mPersisterQueue;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
+            new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<HashMap<String, PackageConfigRecord>> mModified =
+            new SparseArray<>();
+
+    private static File getUserConfigsDir(int userId) {
+        return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
+    }
+
+    PackageConfigPersister(PersisterQueue queue) {
+        mPersisterQueue = queue;
+    }
+
+    @GuardedBy("mLock")
+    void loadUserPackages(int userId) {
+        synchronized (mLock) {
+            final File userConfigsDir = getUserConfigsDir(userId);
+            final File[] configFiles = userConfigsDir.listFiles();
+            if (configFiles == null) {
+                Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir);
+                return;
+            }
+
+            for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) {
+                final File configFile = configFiles[fileIndex];
+                if (DEBUG) {
+                    Slog.d(TAG, "loadPackages: userId=" + userId
+                            + ", configFile=" + configFile.getName());
+                }
+                if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) {
+                    continue;
+                }
+
+                try (InputStream is = new FileInputStream(configFile)) {
+                    final TypedXmlPullParser in = Xml.resolvePullParser(is);
+                    int event;
+                    String packageName = null;
+                    int nightMode = MODE_NIGHT_AUTO;
+                    while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+                            && event != XmlPullParser.END_TAG) {
+                        final String name = in.getName();
+                        if (event == XmlPullParser.START_TAG) {
+                            if (DEBUG) {
+                                Slog.d(TAG, "loadPackages: START_TAG name=" + name);
+                            }
+                            if (TAG_CONFIG.equals(name)) {
+                                for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0;
+                                        --attIdx) {
+                                    final String attrName = in.getAttributeName(attIdx);
+                                    final String attrValue = in.getAttributeValue(attIdx);
+                                    switch (attrName) {
+                                        case ATTR_PACKAGE_NAME:
+                                            packageName = attrValue;
+                                            break;
+                                        case ATTR_NIGHT_MODE:
+                                            nightMode = Integer.parseInt(attrValue);
+                                            break;
+                                    }
+                                }
+                            }
+                        }
+                        XmlUtils.skipCurrentTag(in);
+                    }
+                    if (packageName != null) {
+                        final PackageConfigRecord initRecord =
+                                findRecordOrCreate(mModified, packageName, userId);
+                        initRecord.mNightMode = nightMode;
+                        if (DEBUG) {
+                            Slog.d(TAG, "loadPackages: load one package " + initRecord);
+                        }
+                    }
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } catch (XmlPullParserException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId,
+            String packageName) {
+        synchronized (mLock) {
+            final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId);
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
+            }
+            if (modifiedRecord != null) {
+                container.setOverrideNightMode(modifiedRecord.mNightMode);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void updateFromImpl(String packageName, int userId,
+            ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+        synchronized (mLock) {
+            PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
+            record.mNightMode = impl.getNightMode();
+
+            if (record.isResetNightMode()) {
+                removePackage(record.mName, record.mUserId);
+            } else {
+                final PackageConfigRecord pendingRecord =
+                        findRecord(mPendingWrite, record.mName, record.mUserId);
+                final PackageConfigRecord writeRecord;
+                if (pendingRecord == null) {
+                    writeRecord = findRecordOrCreate(mPendingWrite, record.mName,
+                            record.mUserId);
+                } else {
+                    writeRecord = pendingRecord;
+                }
+                if (writeRecord.mNightMode == record.mNightMode) {
+                    return;
+                }
+                writeRecord.mNightMode = record.mNightMode;
+                if (DEBUG) {
+                    Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
+                }
+                mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void removeUser(int userId) {
+        synchronized (mLock) {
+            final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId);
+            final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId);
+            if ((modifyRecords == null || modifyRecords.size() == 0)
+                    && (writeRecords == null || writeRecords.size() == 0)) {
+                return;
+            }
+            final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords);
+            tempList.forEach((name, record) -> {
+                removePackage(record.mName, record.mUserId);
+            });
+        }
+    }
+
+    @GuardedBy("mLock")
+    void onPackageUninstall(String packageName) {
+        synchronized (mLock) {
+            for (int i = mModified.size() - 1; i > 0; i--) {
+                final int userId = mModified.keyAt(i);
+                removePackage(packageName, userId);
+            }
+        }
+    }
+
+    private void removePackage(String packageName, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId);
+        }
+        final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId);
+        if (record != null) {
+            removeRecord(mPendingWrite, record);
+            mPersisterQueue.removeItems(item ->
+                            item.mRecord.mName == record.mName
+                                    && item.mRecord.mUserId == record.mUserId,
+                    WriteProcessItem.class);
+        }
+
+        final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId);
+        if (modifyRecord != null) {
+            removeRecord(mModified, modifyRecord);
+            mPersisterQueue.addItem(new DeletePackageItem(userId, packageName),
+                    false /* flush */);
+        }
+    }
+
+    // store a changed data so we don't need to get the process
+    static class PackageConfigRecord {
+        final String mName;
+        final int mUserId;
+        int mNightMode;
+
+        PackageConfigRecord(String name, int userId) {
+            mName = name;
+            mUserId = userId;
+        }
+
+        boolean isResetNightMode() {
+            return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+        }
+
+        @Override
+        public String toString() {
+            return "PackageConfigRecord package name: " + mName + " userId " + mUserId
+                    + " nightMode " + mNightMode;
+        }
+    }
+
+    private PackageConfigRecord findRecordOrCreate(
+            SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) {
+        HashMap<String, PackageConfigRecord> records = list.get(userId);
+        if (records == null) {
+            records = new HashMap<>();
+            list.put(userId, records);
+        }
+        PackageConfigRecord record = records.get(name);
+        if (record != null) {
+            return record;
+        }
+        record = new PackageConfigRecord(name, userId);
+        records.put(name, record);
+        return record;
+    }
+
+    private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+            String name, int userId) {
+        HashMap<String, PackageConfigRecord> packages = list.get(userId);
+        if (packages == null) {
+            return null;
+        }
+        return packages.get(name);
+    }
+
+    private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+            PackageConfigRecord record) {
+        final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId);
+        if (processes != null) {
+            processes.remove(record.mName);
+        }
+    }
+
+    private static class DeletePackageItem implements PersisterQueue.WriteQueueItem {
+        final int mUserId;
+        final String mPackageName;
+
+        DeletePackageItem(int userId, String packageName) {
+            mUserId = userId;
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void process() {
+            File userConfigsDir = getUserConfigsDir(mUserId);
+            if (!userConfigsDir.isDirectory()) {
+                return;
+            }
+            final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir,
+                    mPackageName + SUFFIX_FILE_NAME));
+            if (atomicFile.exists()) {
+                atomicFile.delete();
+            }
+        }
+    }
+
+    private class WriteProcessItem implements PersisterQueue.WriteQueueItem {
+        final PackageConfigRecord mRecord;
+
+        WriteProcessItem(PackageConfigRecord record) {
+            mRecord = record;
+        }
+
+        @Override
+        public void process() {
+            // Write out one user.
+            byte[] data = null;
+            synchronized (mLock) {
+                try {
+                    data = saveToXml();
+                } catch (Exception e) {
+                }
+                removeRecord(mPendingWrite, mRecord);
+            }
+            if (data != null) {
+                // Write out xml file while not holding mService lock.
+                FileOutputStream file = null;
+                AtomicFile atomicFile = null;
+                try {
+                    File userConfigsDir = getUserConfigsDir(mRecord.mUserId);
+                    if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) {
+                        Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId
+                                + ": " + userConfigsDir);
+                        return;
+                    }
+                    atomicFile = new AtomicFile(new File(userConfigsDir,
+                            mRecord.mName + SUFFIX_FILE_NAME));
+                    file = atomicFile.startWrite();
+                    file.write(data);
+                    atomicFile.finishWrite(file);
+                } catch (IOException e) {
+                    if (file != null) {
+                        atomicFile.failWrite(file);
+                    }
+                    Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
+                }
+            }
+        }
+
+        private byte[] saveToXml() throws IOException {
+            final ByteArrayOutputStream os = new ByteArrayOutputStream();
+            final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os);
+
+            xmlSerializer.startDocument(null, true);
+            if (DEBUG) {
+                Slog.d(TAG, "Writing package configuration=" + mRecord);
+            }
+            xmlSerializer.startTag(null, TAG_CONFIG);
+            xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
+            xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            xmlSerializer.endTag(null, TAG_CONFIG);
+            xmlSerializer.endDocument();
+            xmlSerializer.flush();
+
+            return os.toByteArray();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ceebe95..6fbeaa4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2656,7 +2656,7 @@
     void addStartingWindowsForVisibleActivities() {
         forAllActivities((r) -> {
             if (r.mVisibleRequested) {
-                r.showStartingWindow(null /* prev */, false /* newTask */, true /*taskSwitch*/);
+                r.showStartingWindow(true /*taskSwitch*/);
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 94e14dd..ef4a40f 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -60,7 +60,7 @@
 
         final Task task = activity.getTask();
         if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
-                activity.token)) {
+                activity.token, theme)) {
             return new ShellStartingSurface(task);
         }
         return null;
@@ -125,7 +125,8 @@
             return mService.mTaskSnapshotController
                     .createStartingSurface(activity, taskSnapshot);
         }
-        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token);
+        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
+                0 /* launchTheme */);
         return new ShellStartingSurface(task);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c8a997..8bd4dfd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -82,6 +82,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -3881,6 +3882,13 @@
         });
     }
 
+    ActivityRecord getTopWaitSplashScreenActivity() {
+        return getActivity((r) -> {
+            return r.mHandleExitSplashScreen
+                    && r.mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+        });
+    }
+
     boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
         return r != null ? r.isFocusable()
@@ -4267,6 +4275,9 @@
             if (mainWindow != null) {
                 info.mainWindowLayoutParams = mainWindow.getAttrs();
             }
+            // If the developer has persist a different configuration, we need to override it to the
+            // starting window because persisted configuration does not effect to Task.
+            info.taskInfo.configuration.setTo(topActivity.getConfiguration());
         }
         final ActivityRecord topFullscreenActivity = getTopFullscreenActivity();
         if (topFullscreenActivity != null) {
@@ -6558,10 +6569,9 @@
                 Slog.i(TAG, "Restarting because process died: " + next);
                 if (!next.hasBeenLaunched) {
                     next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+                } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
                         && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
+                    next.showStartingWindow(false /* taskSwitch */);
                 }
                 mTaskSupervisor.startSpecificActivity(next, true, false);
                 return true;
@@ -6584,8 +6594,7 @@
                 next.hasBeenLaunched = true;
             } else {
                 if (SHOW_APP_STARTING_PREVIEW) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwich */);
+                    next.showStartingWindow(false /* taskSwich */);
                 }
                 if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
             }
@@ -6746,7 +6755,10 @@
                         prev = null;
                     }
                 }
-                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
+                final int splashScreenThemeResId = options != null
+                        ? options.getSplashScreenThemeResId() : 0;
+                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
+                        splashScreenThemeResId);
             }
         } else {
             // If this is the first activity, don't do any fancy animations,
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9fac3f0..c0bce6b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -117,8 +117,11 @@
             return mTaskOrganizer.asBinder();
         }
 
-        void addStartingWindow(Task task, IBinder appToken) {
+        void addStartingWindow(Task task, IBinder appToken, int launchTheme) {
             final StartingWindowInfo info = task.getStartingWindowInfo();
+            if (launchTheme != 0) {
+                info.splashScreenThemeResId = launchTheme;
+            }
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
                 try {
                     mTaskOrganizer.addStartingWindow(info, appToken);
@@ -138,6 +141,16 @@
             });
         }
 
+        void copySplashScreenView(Task task) {
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.copySplashScreenView(task.mTaskId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
+                }
+            });
+        }
+
         SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
             SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
             if (!task.mCreatedByOrganizer && !visible) {
@@ -232,14 +245,18 @@
             mUid = uid;
         }
 
-        void addStartingWindow(Task t, IBinder appToken) {
-            mOrganizer.addStartingWindow(t, appToken);
+        void addStartingWindow(Task t, IBinder appToken, int launchTheme) {
+            mOrganizer.addStartingWindow(t, appToken, launchTheme);
         }
 
         void removeStartingWindow(Task t) {
             mOrganizer.removeStartingWindow(t);
         }
 
+        void copySplashScreenView(Task t) {
+            mOrganizer.copySplashScreenView(t);
+        }
+
         /**
          * Register this task with this state, but doesn't trigger the task appeared callback to
          * the organizer.
@@ -465,14 +482,14 @@
         return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
     }
 
-    boolean addStartingWindow(Task task, IBinder appToken) {
+    boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null || rootTask.mTaskOrganizer == null) {
             return false;
         }
         final TaskOrganizerState state =
                 mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
-        state.addStartingWindow(task, appToken);
+        state.addStartingWindow(task, appToken, launchTheme);
         return true;
     }
 
@@ -486,6 +503,17 @@
         state.removeStartingWindow(task);
     }
 
+    boolean copySplashScreenView(Task task) {
+        final Task rootTask = task.getRootTask();
+        if (rootTask == null || rootTask.mTaskOrganizer == null) {
+            return false;
+        }
+        final TaskOrganizerState state =
+                mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
+        state.copySplashScreenView(task);
+        return true;
+    }
+
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         if (state != null && state.addTask(task)) {
@@ -646,7 +674,7 @@
             // Remove and add for re-ordering.
             mPendingTaskEvents.remove(pending);
         }
-        pending.mForce = force;
+        pending.mForce |= force;
         mPendingTaskEvents.add(pending);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c54c978..5dc5ab7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7514,8 +7514,8 @@
                 if (spec != null) {
                     result.setTo(spec);
                 }
-                spec.scale *= windowState.mGlobalScale;
-                return spec;
+                result.scale *= windowState.mGlobalScale;
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 264a3b4..c362023 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -256,6 +256,7 @@
         }
 
         onConfigurationChanged(atm.getGlobalConfiguration());
+        mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
     }
 
     public void setPid(int pid) {
@@ -802,6 +803,13 @@
         return false;
     }
 
+    void updateNightModeForAllActivities(int nightMode) {
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            r.setOverrideNightMode(nightMode);
+        }
+    }
+
     public void clearPackagePreferredForHomeActivities() {
         synchronized (mAtm.mGlobalLock) {
             for (int i = mActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index a7abf6a..11e3ecf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -30,6 +30,7 @@
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
+        "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
         "com_android_server_input_InputManagerService.cpp",
@@ -54,7 +55,7 @@
         "com_android_server_UsbMidiDevice.cpp",
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_vibrator_VibratorController.cpp",
-        "com_android_server_VibratorManagerService.cpp",
+        "com_android_server_vibrator_VibratorManagerService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 9a8942b..d076434 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,10 +1,6 @@
 # Display
 per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
 
-# Haptics
-per-file com_android_server_vibrator_VibratorController.cpp = michaelwr@google.com
-per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
-
 # Input
 per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
 
diff --git a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
similarity index 100%
rename from packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp
rename to services/core/jni/com_android_server_connectivity_Vpn.cpp
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index a6029cd..89b931d 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -29,7 +29,7 @@
 
 #include <vibratorservice/VibratorHalController.h>
 
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
@@ -73,7 +73,8 @@
               static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
-    vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
+    vibrator::ManagerHalController* manager =
+            android_server_vibrator_VibratorManagerService_getManager();
     if (manager == nullptr) {
         return nullptr;
     }
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
similarity index 89%
rename from services/core/jni/com_android_server_VibratorManagerService.cpp
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index 5dbb71a..a47ab9d 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -26,7 +26,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
 
 namespace android {
 
@@ -64,7 +64,7 @@
     const jobject mCallbackListener;
 };
 
-vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() {
+vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager() {
     std::lock_guard<std::mutex> lock(gManagerMutex);
     return gManager;
 }
@@ -156,10 +156,11 @@
     service->hal()->cancelSynced();
 }
 
+inline static constexpr auto sNativeInitMethodSignature =
+        "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+
 static const JNINativeMethod method_table[] = {
-        {"nativeInit",
-         "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J",
-         (void*)nativeInit},
+        {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
         {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
         {"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities},
         {"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
@@ -168,15 +169,15 @@
         {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
 };
 
-int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
+int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
     sJvm = jvm;
     auto listenerClassName =
-            "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener";
+            "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
     jclass listenerClass = FindClassOrDie(env, listenerClassName);
     sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
 
-    return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
-                                    NELEM(method_table));
+    return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
+                                    method_table, NELEM(method_table));
 }
 
 }; // namespace android
diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
similarity index 84%
rename from services/core/jni/com_android_server_VibratorManagerService.h
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.h
index 22950c5..9924e24 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.h
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -21,7 +21,7 @@
 
 namespace android {
 
-extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager();
+extern vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager();
 
 } // namespace android
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 34f6048..1815f0c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,8 +39,9 @@
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
-int register_android_server_VibratorManagerService(JavaVM* vm, JNIEnv* env);
+int register_android_server_vibrator_VibratorManagerService(JavaVM* vm, JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
+int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -54,10 +55,8 @@
 int register_android_server_security_VerityUtils(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
-        JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
-    JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
@@ -90,9 +89,10 @@
     register_android_server_UsbHostManager(env);
     register_android_server_vr_VrManagerService(env);
     register_android_server_vibrator_VibratorController(vm, env);
-    register_android_server_VibratorManagerService(vm, env);
+    register_android_server_vibrator_VibratorManagerService(vm, env);
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
+    register_android_server_connectivity_Vpn(env);
     register_android_server_devicepolicy_CryptoTestHelper(env);
     register_android_server_ConsumerIrService(env);
     register_android_server_BatteryStatsService(env);
@@ -109,10 +109,8 @@
     register_android_server_security_VerityUtils(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
-    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
-            env);
-    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
-        env);
+    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
+    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
index e27e1b8..1406dbb 100644
--- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -27,6 +27,13 @@
         <xs:attribute type="xs:boolean" name="enabled" use="required" />
     </xs:complexType>
 
+    <xs:complexType name="raw-override-value">
+        <xs:attribute type="xs:string" name="packageName" use="required" />
+        <xs:attribute type="xs:long" name="minVersionCode" />
+        <xs:attribute type="xs:long" name="maxVersionCode" />
+        <xs:attribute type="xs:boolean" name="enabled" use="required" />
+    </xs:complexType>
+
     <xs:complexType name="change-overrides">
         <xs:attribute type="xs:long" name="changeId" use="required"/>
         <xs:element name="validated">
@@ -43,6 +50,13 @@
                 </xs:sequence>
             </xs:complexType>
         </xs:element>
+        <xs:element name="raw">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
     </xs:complexType>
 
     <xs:element name="overrides">
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
index 08b8207..a5ccffc 100644
--- a/services/core/xsd/platform-compat/overrides/schema/current.txt
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -5,9 +5,11 @@
     ctor public ChangeOverrides();
     method public long getChangeId();
     method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+    method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw();
     method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
     method public void setChangeId(long);
     method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+    method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw);
     method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
   }
 
@@ -16,6 +18,11 @@
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
   }
 
+  public static class ChangeOverrides.Raw {
+    ctor public ChangeOverrides.Raw();
+    method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue();
+  }
+
   public static class ChangeOverrides.Validated {
     ctor public ChangeOverrides.Validated();
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
@@ -34,6 +41,18 @@
     method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
   }
 
+  public class RawOverrideValue {
+    ctor public RawOverrideValue();
+    method public boolean getEnabled();
+    method public long getMaxVersionCode();
+    method public long getMinVersionCode();
+    method public String getPackageName();
+    method public void setEnabled(boolean);
+    method public void setMaxVersionCode(long);
+    method public void setMinVersionCode(long);
+    method public void setPackageName(String);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b063e67..498ee38 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -781,8 +781,7 @@
              * however it's too early in the boot process to register with IIpConnectivityMetrics
              * to listen for events.
              */
-            if (Intent.ACTION_USER_STARTED.equals(action)
-                    && userHandle == mOwners.getDeviceOwnerUserId()) {
+            if (Intent.ACTION_USER_STARTED.equals(action) && userHandle == UserHandle.USER_SYSTEM) {
                 synchronized (getLockObject()) {
                     if (isNetworkLoggingEnabledInternalLocked()) {
                         setNetworkLoggingActiveInternal(true);
@@ -6372,7 +6371,7 @@
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
+                () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(
                         caller.getUserId()));
     }
 
@@ -7527,6 +7526,20 @@
         sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
     }
 
+    void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
+        ComponentName receiverComponent = null;
+        if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) {
+            receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, userId);
+        }
+        if (receiverComponent == null) {
+            receiverComponent = getOwnerComponent(userId);
+        }
+        sendActiveAdminCommand(action, extras, userId, receiverComponent);
+    }
+
     private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) {
         sendActiveAdminCommand(action, extras, userId,
                 mOwners.getProfileOwnerComponent(userId));
@@ -8351,6 +8364,7 @@
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
         applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        setNetworkLoggingActiveInternal(false);
     }
 
     @Override
@@ -14131,7 +14145,9 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14139,11 +14155,11 @@
                 // already in the requested state
                 return;
             }
-            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            deviceOwner.isNetworkLoggingEnabled = enabled;
+            final ActiveAdmin activeAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+            activeAdmin.isNetworkLoggingEnabled = enabled;
             if (!enabled) {
-                deviceOwner.numNetworkLoggingNotifications = 0;
-                deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+                activeAdmin.numNetworkLoggingNotifications = 0;
+                activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
             }
             saveSettingsLocked(caller.getUserId());
             setNetworkLoggingActiveInternal(enabled);
@@ -14161,7 +14177,13 @@
         synchronized (getLockObject()) {
             mInjector.binderWithCleanCallingIdentity(() -> {
                 if (active) {
-                    mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal());
+                    if (mNetworkLogger == null) {
+                        final int affectedUserId = getNetworkLoggingAffectedUser();
+                        mNetworkLogger = new NetworkLogger(this,
+                                mInjector.getPackageManagerInternal(),
+                                affectedUserId == UserHandle.USER_SYSTEM
+                                        ? UserHandle.USER_ALL : affectedUserId);
+                    }
                     if (!mNetworkLogger.startNetworkLogging()) {
                         mNetworkLogger = null;
                         Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
@@ -14181,6 +14203,25 @@
         }
     }
 
+    private @UserIdInt int getNetworkLoggingAffectedUser() {
+        synchronized (getLockObject()) {
+            if (mOwners.hasDeviceOwner()) {
+                return mOwners.getDeviceOwnerUserId();
+            } else {
+                return mInjector.binderWithCleanCallingIdentity(
+                        () -> getManagedUserId(UserHandle.USER_SYSTEM));
+            }
+        }
+    }
+
+    private ActiveAdmin getNetworkLoggingControllingAdminLocked() {
+        int affectedUserId = getNetworkLoggingAffectedUser();
+        if (affectedUserId < 0) {
+            return null;
+        }
+        return getDeviceOrProfileOwnerAdminLocked(affectedUserId);
+    }
+
     @Override
     public long forceNetworkLogs() {
         Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()),
@@ -14201,10 +14242,12 @@
     @GuardedBy("getLockObject()")
     private void maybePauseDeviceWideLoggingLocked() {
         if (!areAllUsersAffiliatedWithDeviceLocked()) {
-            Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
-                    + "paused if enabled.");
-            if (mNetworkLogger != null) {
-                mNetworkLogger.pause();
+            if (mOwners.hasDeviceOwner()) {
+                Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
+                        + "paused if enabled.");
+                if (mNetworkLogger != null) {
+                    mNetworkLogger.pause();
+                }
             }
             if (!isOrganizationOwnedDeviceWithManagedProfile()) {
                 Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
@@ -14223,7 +14266,9 @@
             if (allUsersAffiliated || orgOwnedProfileDevice) {
                 mSecurityLogMonitor.resume();
             }
-            if (allUsersAffiliated) {
+            // If there is no device owner, then per-user network logging may be enabled for the
+            // managed profile. In which case, all users do not need to be affiliated.
+            if (allUsersAffiliated || !mOwners.hasDeviceOwner()) {
                 if (mNetworkLogger != null) {
                     mNetworkLogger.resume();
                 }
@@ -14250,7 +14295,9 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
                 || hasCallingOrSelfPermission(permission.MANAGE_USERS));
 
@@ -14260,8 +14307,8 @@
     }
 
     private boolean isNetworkLoggingEnabledInternalLocked() {
-        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+        ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
     }
 
     /*
@@ -14278,9 +14325,13 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
-        checkAllUsersAreAffiliatedWithDevice();
+        if (mOwners.hasDeviceOwner()) {
+            checkAllUsersAreAffiliatedWithDevice();
+        }
 
         synchronized (getLockObject()) {
             if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) {
@@ -14293,34 +14344,35 @@
                     .write();
 
             final long currentTime = System.currentTimeMillis();
-            DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+            DevicePolicyData policyData = getUserData(caller.getUserId());
             if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
                 policyData.mLastNetworkLogsRetrievalTime = currentTime;
-                saveSettingsLocked(UserHandle.USER_SYSTEM);
+                saveSettingsLocked(caller.getUserId());
             }
             return mNetworkLogger.retrieveLogs(batchToken);
         }
     }
 
     private void sendNetworkLoggingNotificationLocked() {
-        final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
+        ensureLocked();
+        final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) {
             return;
         }
-        if (deviceOwner.numNetworkLoggingNotifications >=
-                ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+        if (activeAdmin.numNetworkLoggingNotifications
+                >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
             return;
         }
         final long now = System.currentTimeMillis();
-        if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+        if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
             return;
         }
-        deviceOwner.numNetworkLoggingNotifications++;
-        if (deviceOwner.numNetworkLoggingNotifications
+        activeAdmin.numNetworkLoggingNotifications++;
+        if (activeAdmin.numNetworkLoggingNotifications
                 >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
         } else {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = now;
         }
         final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
         final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
@@ -14340,7 +14392,7 @@
                         .bigText(mContext.getString(R.string.network_logging_notification_text)))
                 .build();
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification);
-        saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+        saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier());
     }
 
     /**
@@ -14404,8 +14456,12 @@
     @Override
     public long getLastNetworkLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
-        return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
+
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
+                || canManageUsers(caller));
+        final int affectedUserId = getNetworkLoggingAffectedUser();
+        return affectedUserId >= 0 ? getUserData(affectedUserId).mLastNetworkLogsRetrievalTime : -1;
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index e9b2d7f..8843a5d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
 
@@ -47,6 +48,10 @@
     private final PackageManagerInternal mPm;
     private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false);
 
+    // The target userId to collect network events on. The target userId will be
+    // {@link android.os.UserHandle#USER_ALL} if network events should be collected for all users.
+    private final int mTargetUserId;
+
     private IIpConnectivityMetrics mIpConnectivityMetrics;
     private ServiceThread mHandlerThread;
     private NetworkLoggingHandler mNetworkLoggingHandler;
@@ -58,6 +63,11 @@
             if (!mIsLoggingEnabled.get()) {
                 return;
             }
+            // If the network logging was enabled by the profile owner, then do not
+            // include events in the personal profile.
+            if (!shouldLogNetworkEvent(uid)) {
+                return;
+            }
             DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount,
                     mPm.getNameForUid(uid), timestamp);
             sendNetworkEvent(dnsEvent);
@@ -68,6 +78,11 @@
             if (!mIsLoggingEnabled.get()) {
                 return;
             }
+            // If the network logging was enabled by the profile owner, then do not
+            // include events in the personal profile.
+            if (!shouldLogNetworkEvent(uid)) {
+                return;
+            }
             ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid),
                     timestamp);
             sendNetworkEvent(connectEvent);
@@ -81,11 +96,17 @@
             msg.setData(bundle);
             mNetworkLoggingHandler.sendMessage(msg);
         }
+
+        private boolean shouldLogNetworkEvent(int uid) {
+            return mTargetUserId == UserHandle.USER_ALL
+                    || mTargetUserId == UserHandle.getUserId(uid);
+        }
     };
 
-    NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) {
+    NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm, int targetUserId) {
         mDpm = dpm;
         mPm = pm;
+        mTargetUserId = targetUserId;
     }
 
     private boolean checkIpConnectivityMetricsService() {
@@ -114,7 +135,7 @@
                         /* allowIo */ false);
                 mHandlerThread.start();
                 mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(),
-                        mDpm);
+                        mDpm, mTargetUserId);
                 mNetworkLoggingHandler.scheduleBatchFinalization();
                 mIsLoggingEnabled.set(true);
                 return true;
@@ -153,7 +174,7 @@
     }
 
     /**
-     * If logs are being collected, keep collecting them but stop notifying the device owner that
+     * If logs are being collected, keep collecting them but stop notifying the admin that
      * new logs are available (since they cannot be retrieved)
      */
     void pause() {
@@ -163,11 +184,11 @@
     }
 
     /**
-     * If logs are being collected, start notifying the device owner when logs are ready to be
+     * If logs are being collected, start notifying the admin when logs are ready to be
      * collected again (if it was paused).
      * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
-     * to notify the device owner. Therefore calling identity should be cleared before calling it
-     * (in case the method is called from a user other than the DO's user).
+     * to notify the admin. Therefore calling identity should be cleared before calling it
+     * (in case the method is called from a user other than the admin's user).
      */
     void resume() {
         if (mNetworkLoggingHandler != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 0a7070f..84e89a0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -50,7 +50,7 @@
     private static final int MAX_EVENTS_PER_BATCH = 1200;
 
     /**
-     * Maximum number of batches to store in memory. If more batches are generated and the DO
+     * Maximum number of batches to store in memory. If more batches are generated and the admin
      * doesn't fetch them, we will discard the oldest one.
      */
     private static final int MAX_BATCHES = 5;
@@ -74,6 +74,7 @@
     private final AlarmManager mAlarmManager;
 
     private long mId;
+    private int mTargetUserId;
 
     private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
         @Override
@@ -82,10 +83,10 @@
                     + mNetworkEvents.size() + " pending events.");
             Bundle notificationExtras = null;
             synchronized (NetworkLoggingHandler.this) {
-                notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+                notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
             }
             if (notificationExtras != null) {
-                notifyDeviceOwner(notificationExtras);
+                notifyDeviceOwnerOrProfileOwner(notificationExtras);
             }
         }
     };
@@ -98,8 +99,8 @@
     private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
 
     /**
-     * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
-     * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
+     * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the admin.
+     * Already retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
      */
     @GuardedBy("this")
     private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
@@ -115,16 +116,18 @@
     @GuardedBy("this")
     private long mLastRetrievedBatchToken;
 
-    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
-        this(looper, dpm, 0 /* event id */);
+    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, int targetUserId) {
+        this(looper, dpm, 0 /* event id */, targetUserId);
     }
 
     @VisibleForTesting
-    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
+    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id,
+            int targetUserId) {
         super(looper);
         this.mDpm = dpm;
         this.mAlarmManager = mDpm.mInjector.getAlarmManager();
         this.mId = id;
+        this.mTargetUserId = targetUserId;
     }
 
     @Override
@@ -137,11 +140,11 @@
                     synchronized (NetworkLoggingHandler.this) {
                         mNetworkEvents.add(networkEvent);
                         if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
-                            notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+                            notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
                         }
                     }
                     if (notificationExtras != null) {
-                        notifyDeviceOwner(notificationExtras);
+                        notifyDeviceOwnerOrProfileOwner(notificationExtras);
                     }
                 }
                 break;
@@ -176,10 +179,10 @@
             if (toWaitNanos > 0) {
                 return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
             }
-            notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+            notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
         }
         if (notificationExtras != null) {
-            notifyDeviceOwner(notificationExtras);
+            notifyDeviceOwnerOrProfileOwner(notificationExtras);
         }
         return 0;
     }
@@ -201,14 +204,15 @@
                     + ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
             mPaused = false;
 
-            // If there is a batch ready that the device owner hasn't been notified about, do it now.
+            // If there is a batch ready that the device owner or profile owner hasn't been
+            // notified about, do it now.
             if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
                 scheduleBatchFinalization();
-                notificationExtras = buildDeviceOwnerMessageLocked();
+                notificationExtras = buildAdminMessageLocked();
             }
         }
         if (notificationExtras != null) {
-            notifyDeviceOwner(notificationExtras);
+            notifyDeviceOwnerOrProfileOwner(notificationExtras);
         }
     }
 
@@ -219,8 +223,8 @@
     }
 
     @GuardedBy("this")
-    /** @returns extras if a message should be sent to the device owner */
-    private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
+    /** @return extras if a message should be sent to the device owner or profile owner */
+    private Bundle finalizeBatchAndBuildAdminMessageLocked() {
         mLastFinalizationNanos = System.nanoTime();
         Bundle notificationExtras = null;
         if (mNetworkEvents.size() > 0) {
@@ -243,10 +247,10 @@
             mBatches.append(mCurrentBatchToken, mNetworkEvents);
             mNetworkEvents = new ArrayList<>();
             if (!mPaused) {
-                notificationExtras = buildDeviceOwnerMessageLocked();
+                notificationExtras = buildAdminMessageLocked();
             }
         } else {
-            // Don't notify the DO, since there are no events; DPC can still retrieve
+            // Don't notify the admin, since there are no events; DPC can still retrieve
             // the last full batch if not paused.
             Slog.d(TAG, "Was about to finalize the batch, but there were no events to send to"
                     + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
@@ -257,9 +261,9 @@
     }
 
     @GuardedBy("this")
-    /** Build extras notification to the DO. Should only be called when there
+    /** Build extras notification to the admin. Should only be called when there
         is a batch available. */
-    private Bundle buildDeviceOwnerMessageLocked() {
+    private Bundle buildAdminMessageLocked() {
         final Bundle extras = new Bundle();
         final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
         extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
@@ -267,16 +271,18 @@
         return extras;
     }
 
-    /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may
-        call into NetworkLoggingHandler. */
-    private void notifyDeviceOwner(Bundle extras) {
-        Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
-                + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+    /** Sends a notification to the device owner or profile owner. Should not hold locks as
+        DevicePolicyManagerService may call into NetworkLoggingHandler. */
+    private void notifyDeviceOwnerOrProfileOwner(Bundle extras) {
         if (Thread.holdsLock(this)) {
             Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held");
             return;
         }
-        mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+        Slog.d(TAG, "Sending network logging batch broadcast to device owner or profile owner, "
+                + "batchToken: "
+                + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+        mDpm.sendDeviceOwnerOrProfileOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE,
+                extras, mTargetUserId);
     }
 
     synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 1801f3b..a2768c6 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,10 @@
 import android.Manifest;
 import android.content.Context;
 import android.os.ISystemConfig;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,6 +68,22 @@
             return SystemConfig.getInstance()
                     .getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
         }
+
+        @Override
+        public int[] getSystemPermissionUids(String permissionName) {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS,
+                    "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS");
+            final List<Integer> uids = new ArrayList<>();
+            final SparseArray<ArraySet<String>> systemPermissions =
+                    SystemConfig.getInstance().getSystemPermissions();
+            for (int i = 0; i < systemPermissions.size(); i++) {
+                final ArraySet<String> permissions = systemPermissions.valueAt(i);
+                if (permissions != null && permissions.contains(permissionName)) {
+                    uids.add(systemPermissions.keyAt(i));
+                }
+            }
+            return ArrayUtils.convertToIntArray(uids);
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a0e5c5d..bd20464 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -195,6 +195,7 @@
 import com.android.server.uri.UriGrantsManagerService;
 import com.android.server.usage.UsageStatsService;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.vibrator.VibratorManagerService;
 import com.android.server.vr.VrManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.ActivityTaskManagerService;
@@ -1295,6 +1296,7 @@
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
         IpSecService ipSecService = null;
+        VpnManagerService vpnManager = null;
         VcnManagementService vcnManagement = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
@@ -1883,6 +1885,15 @@
             networkPolicy.bindConnectivityManager(connectivity);
             t.traceEnd();
 
+            t.traceBegin("StartVpnManagerService");
+            try {
+                vpnManager = VpnManagerService.create(context);
+                ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
+            } catch (Throwable e) {
+                reportWtf("starting VPN Manager Service", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartVcnManagementService");
             try {
                 vcnManagement = VcnManagementService.create(context);
@@ -2611,6 +2622,7 @@
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
         final IpSecService ipSecServiceF = ipSecService;
+        final VpnManagerService vpnManagerF = vpnManager;
         final VcnManagementService vcnManagementF = vcnManagement;
         final WindowManagerService windowManagerF = wm;
         final ConnectivityManager connectivityF = (ConnectivityManager)
@@ -2725,6 +2737,15 @@
                 reportWtf("making Connectivity Service ready", e);
             }
             t.traceEnd();
+            t.traceBegin("MakeVpnManagerServiceReady");
+            try {
+                if (vpnManagerF != null) {
+                    vpnManagerF.systemReady();
+                }
+            } catch (Throwable e) {
+                reportWtf("making VpnManagerService ready", e);
+            }
+            t.traceEnd();
             t.traceBegin("MakeVcnManagementServiceReady");
             try {
                 if (vcnManagementF != null) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index c2e0b77..2d23fb4 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -334,6 +334,7 @@
                     this[0] = PackageUserState()
                 }
             }
+            whenever(getInstantApp(anyInt())) { false }
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index a76152c..a92ab9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -104,14 +104,14 @@
 
             userSelectionStates[1] = DomainVerificationUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
-                isDisallowLinkHandling = false
+                isLinkHandlingAllowed = true
             }
         }
         val stateOne = mockEmptyPkgState(1).apply {
             // It's valid to have a user selection without any autoVerify domains
             userSelectionStates[1] = DomainVerificationUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
-                isDisallowLinkHandling = true
+                isLinkHandlingAllowed = false
             }
         }
 
@@ -137,13 +137,15 @@
                         hasAutoVerifyDomains="true"
                         >
                         <state>
-                            <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
-                            <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+                            <domain name="example.com" state="${
+                                DomainVerificationManager.STATE_SUCCESS}"/>
+                            <domain name="example.org" state="${
+                                DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
                             <not-domain name="not-domain.com" state="1"/>
                             <domain name="missing-state.com"/>
                         </state>
                         <user-states>
-                            <user-state userId="1" disallowLinkHandling="false">
+                            <user-state userId="1" allowLinkHandling="true">
                                 <enabled-hosts>
                                     <host name="example-user1.com"/>
                                     <not-host name="not-host.com"/>
@@ -171,7 +173,7 @@
                         >
                         <state/>
                         <user-states>
-                            <user-state userId="1" disallowLinkHandling="true">
+                            <user-state userId="1" allowLinkHandling="false">
                                 <enabled-hosts>
                                     <host name="example-user1.com"/>
                                     <host name="example-user1.org"/>
@@ -214,9 +216,9 @@
         stateMap["$packageName.com"] = id
         userSelectionStates[id] = DomainVerificationUserState(id).apply {
             addHosts(setOf("$packageName-user.com"))
-            isDisallowLinkHandling = true
+            isLinkHandlingAllowed = true
         }
     }
 
-    private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+    private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 5629d1c..48518f46 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -244,6 +244,7 @@
                     this[0] = PackageUserState()
                 }
             }
+            whenever(getInstantApp(anyInt())) { false }
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
new file mode 100644
index 0000000..d2ab646
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.location.injector;
+
+public class FakeDeviceIdleHelper extends DeviceIdleHelper {
+
+    private boolean mDeviceIdle = false;
+
+    public void setIdle(boolean deviceIdle) {
+        mDeviceIdle = deviceIdle;
+        notifyDeviceIdleChanged();
+    }
+
+    @Override
+    protected void registerInternal() {}
+
+    @Override
+    protected void unregisterInternal() {}
+
+    @Override
+    public boolean isDeviceIdle() {
+        return mDeviceIdle;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
new file mode 100644
index 0000000..b1d921a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class FakeDeviceStationaryHelper extends DeviceStationaryHelper {
+
+    private final CopyOnWriteArrayList<DeviceIdleInternal.StationaryListener> mListeners =
+            new CopyOnWriteArrayList<>();
+
+    private boolean mStationary = false;
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+            listener.onDeviceStationaryChanged(mStationary);
+        }
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public void setStationary(boolean stationary) {
+        synchronized (mListeners) {
+            mStationary = stationary;
+        }
+
+        for (DeviceIdleInternal.StationaryListener listener : mListeners) {
+            listener.onDeviceStationaryChanged(stationary);
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 2822d5c..1f102ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -28,6 +28,8 @@
     private final FakeAppForegroundHelper mAppForegroundHelper;
     private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
     private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
+    private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
+    private final FakeDeviceIdleHelper mDeviceIdleHelper;
     private final LocationAttributionHelper mLocationAttributionHelper;
     private final FakeEmergencyHelper mEmergencyHelper;
     private final LocationUsageLogger mLocationUsageLogger;
@@ -45,6 +47,8 @@
         mAppForegroundHelper = new FakeAppForegroundHelper();
         mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
+        mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
+        mDeviceIdleHelper = new FakeDeviceIdleHelper();
         mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
@@ -91,6 +95,16 @@
     }
 
     @Override
+    public FakeDeviceStationaryHelper getDeviceStationaryHelper() {
+        return mDeviceStationaryHelper;
+    }
+
+    @Override
+    public FakeDeviceIdleHelper getDeviceIdleHelper() {
+        return mDeviceIdleHelper;
+    }
+
+    @Override
     public LocationAttributionHelper getLocationAttributionHelper() {
         return mLocationAttributionHelper;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
new file mode 100644
index 0000000..c3cca64
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider;
+
+import static com.android.server.location.LocationUtils.createLocation;
+import static com.android.server.location.LocationUtils.createLocationResult;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.TestInjector;
+import com.android.server.location.test.FakeProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Random;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StationaryThrottlingLocationProviderTest {
+
+    private static final String TAG = "StationaryThrottlingLocationProviderTest";
+
+    private Random mRandom;
+    private TestInjector mInjector;
+    private FakeProvider mDelegateProvider;
+
+    private @Mock AbstractLocationProvider.Listener mListener;
+    private @Mock FakeProvider.FakeProviderInterface mDelegate;
+
+    private StationaryThrottlingLocationProvider mProvider;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+
+        mInjector = new TestInjector();
+        mDelegateProvider = new FakeProvider(mDelegate);
+
+        mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
+                mDelegateProvider, new LocationEventLog());
+        mProvider.getController().setListener(mListener);
+        mProvider.getController().start();
+    }
+
+    @After
+    public void tearDown() {
+        mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
+        mProvider.getController().stop();
+    }
+
+    @Test
+    public void testThrottle() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(false);
+        verify(mDelegate, times(2)).onSetRequest(request);
+        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testThrottle_NoInitialLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+        verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(false);
+        verify(mDelegate, times(2)).onSetRequest(request);
+        verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_noLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+        verify(mListener, never()).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(0)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_oldLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        Location l = createLocation("test_provider", mRandom);
+        l.setElapsedRealtimeNanos(0);
+
+        LocationResult loc = LocationResult.wrap(l);
+        mDelegateProvider.reportLocation(loc);
+        verify(mListener, times(1)).onReportLocation(loc);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_locationSettingsIgnored() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+                50).setLocationSettingsIgnored(true).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        LocationResult loc = createLocationResult("test_provider", mRandom);
+        mDelegateProvider.reportLocation(loc);
+        verify(mListener, times(1)).onReportLocation(loc);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 6561778..f1402ea 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -3,5 +3,4 @@
 per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
 per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
-per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
index d0767cc..c165c66 100644
--- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -22,6 +22,7 @@
     private boolean mIsDebuggable;
     private int mTargetSdk;
     private String mPackageName;
+    private long mVersionCode;
 
     private ApplicationInfoBuilder() {
         mTargetSdk = -1;
@@ -46,6 +47,11 @@
         return this;
     }
 
+    ApplicationInfoBuilder withVersionCode(Long versionCode) {
+        mVersionCode = versionCode;
+        return this;
+    }
+
     ApplicationInfo build() {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         if (mIsDebuggable) {
@@ -53,6 +59,7 @@
         }
         applicationInfo.packageName = mPackageName;
         applicationInfo.targetSdkVersion = mTargetSdk;
+        applicationInfo.longVersionCode = mVersionCode;
         return applicationInfo;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index a53ff9b..8b0e948 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -25,6 +26,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +49,7 @@
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -83,6 +87,8 @@
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
         ChangeIdStateCache.disable();
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenThrow(new NameNotFoundException());
     }
 
     @Test
@@ -163,6 +169,10 @@
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addDisabledChangeWithId(1234L)
                 .build();
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", true);
 
@@ -177,6 +187,10 @@
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addEnabledChangeWithId(1234L)
                 .build();
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", false);
 
@@ -191,6 +205,10 @@
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
         compatConfig.forceNonDebuggableFinalForTest(false);
 
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", false);
 
@@ -265,6 +283,71 @@
     }
 
     @Test
+    public void testOverrideWithAppVersion() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.installed.foo")
+                .withVersionCode(100L)
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override that doesn't include the installed app version
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L,
+                        new PackageOverride.Builder()
+                                .setMaxVersionCode(99L)
+                                .setEnabled(true)
+                                .build()));
+        compatConfig.addOverrides(config, "com.installed.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        // Add override that does include the installed app version
+        config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L,
+                        new PackageOverride.Builder()
+                                .setMinVersionCode(100L)
+                                .setMaxVersionCode(100L)
+                                .setEnabled(true)
+                                .build()));
+        compatConfig.addOverrides(config, "com.installed.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.notinstalled.foo")
+                .withVersionCode(100L)
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override before the app is available.
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L, new PackageOverride.Builder()
+                        .setMaxVersionCode(99L)
+                        .setEnabled(true)
+                        .build()));
+        compatConfig.addOverrides(config, "com.notinstalled.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        // Pretend the app is now installed.
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        compatConfig.recheckOverrides("com.notinstalled.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+    }
+
+    @Test
     public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception {
         ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
                 .withPackageName("com.installedapp.foo")
@@ -384,6 +467,8 @@
         ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
                 .withPackageName("com.some.package")
                 .build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(applicationInfo);
 
         assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
         assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
@@ -404,6 +489,8 @@
                 .withPackageName("foo.bar")
                 .withTargetSdk(2)
                 .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
 
         assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse();
         assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -425,7 +512,8 @@
                 .withPackageName("foo.bar")
                 .withTargetSdk(2)
                 .build();
-
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
         assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1);
         assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue();
         assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -533,22 +621,114 @@
                 + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
                 + "            </override-value>\n"
                 + "        </validated>\n"
-                + "        <deferred>\n"
-                + "        </deferred>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
                 + "    </change-overrides>\n"
                 + "    <change-overrides changeId=\"2\">\n"
                 + "        <validated>\n"
                 + "        </validated>\n"
-                + "        <deferred>\n"
-                + "            <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
-                + "            </override-value>\n"
-                + "        </deferred>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"bar.baz\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
                 + "    </change-overrides>\n"
                 + "</overrides>\n");
     }
 
     @Test
-    public void testLoadOverrides() throws Exception {
+    public void testSaveOverridesWithRanges() throws Exception {
+        File overridesFile = new File(createTempDir(), "overrides.xml");
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+
+        compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L,
+                new PackageOverride.Builder()
+                        .setMinVersionCode(99L)
+                        .setMaxVersionCode(101L)
+                        .setEnabled(true)
+                        .build())), "foo.bar");
+
+        assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n");
+    }
+
+    @Test
+    public void testLoadOverridesRaw() throws Exception {
+        File tempDir = createTempDir();
+        File overridesFile = new File(tempDir, "overrides.xml");
+        // Change 1 is enabled for foo.bar (validated)
+        // Change 2 is disabled for bar.baz (deferred)
+        String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+                + "            </override-value>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "    <change-overrides changeId=\"2\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"bar.baz\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n";
+        writeToFile(tempDir, "overrides.xml", xmlData);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("foo.bar")
+                .withVersionCode(100L)
+                .debuggable()
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+        assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
+
+        compatConfig.recheckOverrides("foo.bar");
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testLoadOverridesDeferred() throws Exception {
         File tempDir = createTempDir();
         File overridesFile = new File(tempDir, "overrides.xml");
         // Change 1 is enabled for foo.bar (validated)
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index a1b2dc8..799b067 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -196,6 +196,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -208,6 +211,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -219,6 +225,9 @@
     public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -244,6 +253,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -252,9 +264,12 @@
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+    public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -280,6 +295,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
@@ -299,6 +317,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -318,6 +339,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
@@ -336,6 +360,9 @@
     public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         // Listener not called when a non existing override is removed.
         verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7597cbf..4002f5b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4329,6 +4329,68 @@
     }
 
     @Test
+    public void testSetNetworkLoggingEnabled_asPo() throws Exception {
+        final int managedProfileUserId = CALLER_USER_HANDLE;
+        final int managedProfileAdminUid =
+                UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.packageName = admin1.getPackageName();
+        addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
+    public void testSetNetworkLoggingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+        // Setup profile owner on organization-owned device
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mContext.packageName = admin1.getPackageName();
+        mContext.applicationInfo = new ApplicationInfo();
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
     public void testGetBindDeviceAdminTargetUsers() throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 7506dd4..743b25f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -125,7 +125,7 @@
     private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
         // GIVEN a handler with events
         NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
-                mDpmTestable, startingId);
+                mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE);
         // GIVEN network events are sent to the handler.
         for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
             ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 23a4c2f..732c08c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -30,6 +30,7 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -43,6 +44,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessControllerTest {
     private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index f0b4f1b..9396ed2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -30,6 +30,7 @@
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
 import android.util.MathUtils;
 import android.util.Spline;
 
@@ -42,6 +43,7 @@
 import java.util.Arrays;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BrightnessMappingStrategyTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 640d6e5..1c55072 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -45,7 +45,9 @@
 import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.MessageQueue;
 import android.os.Process;
+import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
@@ -76,10 +78,15 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.time.Duration;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.LongStream;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -193,8 +200,8 @@
         verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
         List<DisplayViewport> viewports = viewportCaptor.getValue();
 
-        // Expect to receive 2 viewports: internal, and virtual
-        assertEquals(2, viewports.size());
+        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+        assertTrue(viewports.size() >= 2);
 
         DisplayViewport virtualViewport = null;
         DisplayViewport internalViewport = null;
@@ -202,7 +209,10 @@
             DisplayViewport v = viewports.get(i);
             switch (v.type) {
                 case DisplayViewport.VIEWPORT_INTERNAL: {
+                    // If more than one internal viewport, this will get overwritten several times,
+                    // which for the purposes of this test is fine.
                     internalViewport = v;
+                    assertTrue(internalViewport.valid);
                     break;
                 }
                 case DisplayViewport.VIEWPORT_EXTERNAL: {
@@ -219,9 +229,6 @@
         assertNotNull(internalViewport);
         assertNotNull(virtualViewport);
 
-        // INTERNAL
-        assertTrue(internalViewport.valid);
-
         // VIRTUAL
         assertEquals(height, virtualViewport.deviceHeight);
         assertEquals(width, virtualViewport.deviceWidth);
@@ -243,10 +250,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
 
         final int displayIds[] = bs.getDisplayIds();
-        assertEquals(1, displayIds.length);
-        final int displayId = displayIds[0];
-        DisplayInfo info = bs.getDisplayInfo(displayId);
-        assertEquals(info.type, Display.TYPE_INTERNAL);
+        final int size = displayIds.length;
+        assertTrue(size > 0);
+        for (int i = 0; i < size; i++) {
+            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+            assertEquals(info.type, Display.TYPE_INTERNAL);
+        }
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -257,16 +266,22 @@
         verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
         List<DisplayViewport> viewports = viewportCaptor.getValue();
 
-        // Expect to receive actual viewports: 1 internal
-        assertEquals(1, viewports.size());
+        // Due to the nature of foldables, we may have a different number of viewports than
+        // displays, just verify there's at least one.
+        final int viewportSize = viewports.size();
+        assertTrue(viewportSize > 0);
 
-        DisplayViewport internalViewport = viewports.get(0);
+        // Now verify that each viewport's displayId is valid.
+        Arrays.sort(displayIds);
+        for (int i = 0; i < viewportSize; i++) {
+            DisplayViewport internalViewport = viewports.get(i);
 
-        // INTERNAL is the only one actual display.
-        assertNotNull(internalViewport);
-        assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
-        assertTrue(internalViewport.valid);
-        assertEquals(displayId, internalViewport.displayId);
+            // INTERNAL is the only one actual display.
+            assertNotNull(internalViewport);
+            assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
+            assertTrue(internalViewport.valid);
+            assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0);
+        }
     }
 
     @Test
@@ -486,7 +501,6 @@
      * Tests that collection of display color sampling results are sensible.
      */
     @Test
-    @FlakyTest(bugId = 172555744)
     public void testDisplayedContentSampling() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -937,8 +951,22 @@
         // Would prefer to call displayManager.onStart() directly here but it performs binderService
         // registration which triggers security exceptions when running from a test.
         handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
-        // flush the handler
-        handler.runWithScissors(() -> {}, 0 /* now */);
+        waitForIdleHandler(handler, Duration.ofSeconds(1));
+    }
+
+    private void waitForIdleHandler(Handler handler, Duration timeout) {
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.addIdleHandler(() -> {
+            latch.countDown();
+            // Remove idle handler
+            return false;
+        });
+        try {
+            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Interrupted unexpectedly: " + e);
+        }
     }
 
     private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2e3178b..ee0f2e8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -41,12 +41,13 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -82,6 +83,7 @@
 import java.util.stream.Collectors;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayModeDirectorTest {
     // The tolerance within which we consider something approximately equals.
@@ -588,6 +590,12 @@
 
     @Test
     public void testSensorRegistration() {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
         setPeakRefreshRate(90 /*fps*/);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ac45017..ece0a627 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -20,17 +20,23 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.InputStream;
 import java.io.OutputStream;
 
+@SmallTest
+@Presubmit
 public class LogicalDisplayTest {
     private static final int DISPLAY_ID = 0;
     private static final int LAYER_STACK = 0;
@@ -52,6 +58,9 @@
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
 
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
         DisplayDeviceRepository repo = new DisplayDeviceRepository(
                 new DisplayManagerService.SyncRoot(),
                 new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 196454b..d72606a 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.hardware.display.BrightnessConfiguration;
+import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
@@ -40,6 +41,7 @@
 import java.nio.charset.StandardCharsets;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class PersistentDataStoreTest {
     private PersistentDataStore mDataStore;
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 4bbf96f..7771afc 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
@@ -684,8 +684,8 @@
                     newAddFontFamilyRequest("<family lang='en'>"
                             + "  <font>test.ttf</font>"
                             + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+            fail("Expect NullPointerException");
+        } catch (NullPointerException e) {
             // Expect
         }
     }
@@ -703,9 +703,10 @@
             dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
                     + "  <font>test.ttf</font>"
                     + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 9f0d982..5342486 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_INVALID;
@@ -49,6 +50,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
@@ -1586,4 +1588,46 @@
         assertThat(features.contains(
                 Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)).isFalse();
     }
+
+    @Test
+    public void doesNotSupportRecordTvScreen() {
+        HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress,
+                Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+        mNativeWrapper.onCecMessage(recordTvScreen);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_RECORD_TV_SCREEN,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void shouldHandleUserControlPressedAndReleased() {
+        HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                ADDR_TV, mPlaybackLogicalAddress,
+                HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                ADDR_TV, mPlaybackLogicalAddress);
+
+        mNativeWrapper.onCecMessage(userControlPressed);
+        mTestLooper.dispatchAll();
+
+        // Move past the follower safety timeout
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(2));
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(userControlReleased);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbortPressed = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_PRESSED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        HdmiCecMessage featureAbortReleased = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_RELEASED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0f527f3..4623eb5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,8 +15,11 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -27,6 +30,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.IThermalService;
@@ -66,6 +70,7 @@
     private IPowerManager mIPowerManagerMock;
     @Mock
     private IThermalService mIThermalServiceMock;
+    @Mock private AudioManager mAudioManager;
 
     @Before
     public void setUp() {
@@ -101,11 +106,21 @@
                     }
 
                     @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
+
+                    @Override
                     protected PowerManager getPowerManager() {
                         return powerManager;
                     }
 
                     @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
                     protected HdmiCecConfig getHdmiCecConfig() {
                         return hdmiCecConfig;
                     }
@@ -121,9 +136,11 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceTv);
-        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
         hdmiPortInfos[0] =
                 new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+        hdmiPortInfos[1] =
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -302,4 +319,185 @@
         assertThat(features.contains(Constants.RC_PROFILE_TV_THREE)).isFalse();
         assertThat(features.contains(Constants.RC_PROFILE_TV_FOUR)).isFalse();
     }
+
+    @Test
+    public void startArcAction_enable_noAudioDevice() {
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+
+    @Test
+    public void startArcAction_disable_noAudioDevice() {
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_enable_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_disable_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_enable_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_disable_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+    }
+
+    @Test
+    public void handleInitiateArc_noAudioDevice() {
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+    }
+
+    @Test
+    public void handleInitiateArc_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+    }
+
+    @Test
+    public void handleInitiateArc_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+    }
+
+    @Test
+    public void supportsRecordTvScreen() {
+        HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress,
+                Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+        mNativeWrapper.onCecMessage(recordTvScreen);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mTvLogicalAddress, ADDR_RECORDER_1, Constants.MESSAGE_RECORD_TV_SCREEN,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
index 32445fd..2eedc32 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -19,19 +19,17 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.security.GeneralSecurityException;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
 
-import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
  * atest FrameworksServicesTests:RebootEscrowDataTest
@@ -41,22 +39,18 @@
     private RebootEscrowKey mKey;
     private SecretKey mKeyStoreEncryptionKey;
 
-    private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
-        KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
-        generator.init(new KeyGenParameterSpec.Builder(
-                "reboot_escrow_data_test_key",
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                .setKeySize(256)
-                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                .build());
-        return generator.generateKey();
-    }
+    // Hex encoding of a randomly generated AES key for test.
+    private static final byte[] TEST_AES_KEY = new byte[] {
+            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
+            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
+            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
+            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
+    };
 
     @Before
     public void generateKey() throws Exception {
         mKey = RebootEscrowKey.generate();
-        mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
+        mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
     }
 
     private static byte[] getTestSp() {
@@ -114,4 +108,23 @@
         assertThat(decrypted, is(testSp));
     }
 
+    @Test
+    public void fromEncryptedData_legacyVersion_success() throws Exception {
+        byte[] testSp = getTestSp();
+        byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp);
+
+        // Write a legacy blob encrypted only by k_s.
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+        dos.writeInt(1);
+        dos.writeByte(3);
+        dos.write(ksEncryptedBlob);
+        byte[] legacyBlob = bos.toByteArray();
+
+        RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null);
+
+        assertThat(actual.getSpVersion(), is((byte) 3));
+        assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes()));
+        assertThat(actual.getSyntheticPassword(), is(testSp));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 0dcd608..a1b2f38 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -39,6 +39,7 @@
 import com.android.frameworks.servicestests.R;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
@@ -55,6 +56,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
 import java.util.Map;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -64,6 +67,9 @@
 public class DexMetadataHelperTest {
     private static final String APK_FILE_EXTENSION = ".apk";
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+    private static final String DEX_METADATA_PACKAGE_NAME =
+            "com.android.frameworks.servicestests.install_split";
+    private static long DEX_METADATA_VERSION_CODE = 30;
 
     @Rule
     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -76,12 +82,46 @@
     }
 
     private File createDexMetadataFile(String apkFileName) throws IOException {
+        return createDexMetadataFile(apkFileName, /*validManifest=*/true);
+    }
+
+    private File createDexMetadataFile(String apkFileName, boolean validManifest) throws IOException
+            {
+        return createDexMetadataFile(apkFileName,DEX_METADATA_PACKAGE_NAME,
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, validManifest);
+    }
+
+    private File createDexMetadataFile(String apkFileName, String packageName, Long versionCode,
+            boolean emptyManifest, boolean validManifest) throws IOException {
         File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
                 DEX_METADATA_FILE_EXTENSION));
         try (FileOutputStream fos = new FileOutputStream(dmFile)) {
             try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
                 zipOs.putNextEntry(new ZipEntry("primary.prof"));
                 zipOs.closeEntry();
+
+                if (validManifest) {
+                    zipOs.putNextEntry(new ZipEntry("manifest.json"));
+                    if (!emptyManifest) {
+                      String manifestStr = "{";
+
+                      if (packageName != null) {
+                          manifestStr += "\"packageName\": " + "\"" + packageName + "\"";
+
+                          if (versionCode != null) {
+                            manifestStr += ", ";
+                          }
+                      }
+                      if (versionCode != null) {
+                        manifestStr += " \"versionCode\": " + versionCode;
+                      }
+
+                      manifestStr += "}";
+                      byte[] bytes = manifestStr.getBytes(StandardCharsets.UTF_8);
+                      zipOs.write(bytes, /*off=*/0, /*len=*/bytes.length);
+                    }
+                    zipOs.closeEntry();
+                }
             }
         }
         return dmFile;
@@ -96,17 +136,38 @@
         return outFile;
     }
 
+    private static void validatePackageDexMetadata(AndroidPackage pkg, boolean requireManifest)
+            throws PackageParserException {
+        Collection<String> apkToDexMetadataList =
+                AndroidPackageUtils.getPackageDexMetadata(pkg).values();
+        String packageName = pkg.getPackageName();
+        long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
+        for (String dexMetadata : apkToDexMetadataList) {
+            DexMetadataHelper.validateDexMetadataFile(
+                    dexMetadata, packageName, versionCode, requireManifest);
+        }
+    }
+
+    private static void validatePackageDexMetatadataVaryingRequireManifest(ParsedPackage pkg)
+            throws PackageParserException {
+        validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+        validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+    }
+
     @Test
     public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(1, packageDexMetadata.size());
         String baseDexMetadata = packageDexMetadata.get(pkg.getBaseApkPath());
         assertNotNull(baseDexMetadata);
         assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.getBaseApkPath()));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -116,7 +177,7 @@
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
         createDexMetadataFile("install_split_feature_a.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(2, packageDexMetadata.size());
@@ -127,6 +188,9 @@
         String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
         assertNotNull(splitDexMetadata);
         assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -135,7 +199,7 @@
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_feature_a.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(1, packageDexMetadata.size());
@@ -143,6 +207,9 @@
         String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
         assertNotNull(splitDexMetadata);
         assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -151,9 +218,17 @@
         File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
         Files.createFile(invalidDmFile.toPath());
         try {
-            ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(mTmpDir, 0 /* flags */, false);
-            AndroidPackageUtils.validatePackageDexMetadata(pkg);
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+            fail("Should fail validation: empty .dm file");
         } catch (PackageParserException e) {
             assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
         }
@@ -169,9 +244,112 @@
         Files.createFile(invalidDmFile.toPath());
 
         try {
-            ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(mTmpDir, 0 /* flags */, false);
-            AndroidPackageUtils.validatePackageDexMetadata(pkg);
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileInvalidManifest()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing manifest.json in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileEmptyManifest()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
+                /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty manifest.json in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileBadPackageName()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: bad package name in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileBadVersionCode()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+                /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: bad version code in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileMissingPackageName()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing package name in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileMissingVersionCode()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+                /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing version code in the .dm archive");
         } catch (PackageParserException e) {
             assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
         }
@@ -184,7 +362,7 @@
 
         try {
             DexMetadataHelper.validateDexPaths(mTmpDir.list());
-            fail("Should fail validation");
+            fail("Should fail validation: .dm filename has no match against .apk");
         } catch (IllegalStateException e) {
             // expected.
         }
@@ -200,7 +378,7 @@
 
         try {
             DexMetadataHelper.validateDexPaths(mTmpDir.list());
-            fail("Should fail validation");
+            fail("Should fail validation: split .dm filename unmatched against .apk");
         } catch (IllegalStateException e) {
             // expected.
         }
@@ -211,7 +389,7 @@
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         final File dm = createDexMetadataFile("install_split_base.apk");
         final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
-                ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */);
+                ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, /*flags=*/0);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
@@ -228,7 +406,7 @@
         try (FileInputStream is = new FileInputStream(base)) {
             final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
                     ParseTypeImpl.forDefaultParsing().reset(), is.getFD(),
-                    base.getAbsolutePath(), /* flags */ 0);
+                    base.getAbsolutePath(), /*flags=*/0);
             if (result.isError()) {
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
index 5f64249..22c38c1 100644
--- a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
@@ -19,15 +19,21 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.rotationresolver.RotationResolverInternal;
 import android.view.Surface;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -41,75 +47,81 @@
  */
 @SmallTest
 public class RotationResolverManagerPerUserServiceTest {
-
-    @Mock
-    Context mContext;
+    private static final String PACKAGE_NAME = "test_pkg";
+    private static final String CLASS_NAME = "test_class";
 
     @Mock
     RotationResolverInternal.RotationResolverCallbackInternal mMockCallbackInternal;
-
     @Mock
-    ComponentName mMockComponentName;
+    PackageManager mMockPackageManager;
 
+    private Context mContext;
     private CancellationSignal mCancellationSignal;
-
-    private RotationResolverManagerPerUserService mSpyService;
+    private RotationResolverManagerPerUserService mService;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
-        // setup context mock
+        // setup context.
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        doReturn(PACKAGE_NAME).when(mMockPackageManager).getRotationResolverPackageName();
+        doReturn(createTestingResolveInfo()).when(mMockPackageManager).resolveServiceAsUser(any(),
+                anyInt(), anyInt());
+        doReturn(mMockPackageManager).when(mContext).getPackageManager();
         doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
 
         // setup a spy for the RotationResolverManagerPerUserService.
         final RotationResolverManagerService mainService = new RotationResolverManagerService(
                 mContext);
-        final RotationResolverManagerPerUserService mService =
-                new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
-                        mContext.getUserId());
+        mService = new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
+                mContext.getUserId());
 
         mCancellationSignal = new CancellationSignal();
-        mSpyService = Mockito.spy(mService);
 
-        mSpyService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
+        this.mService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
                 mMockCallbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, "", 1000L,
                 mCancellationSignal);
 
-        mSpyService.getMaster().mIsServiceEnabled = true;
+        this.mService.getMaster().mIsServiceEnabled = true;
 
-        mSpyService.mRemoteService = new MockRemoteRotationResolverService(mContext,
-                mMockComponentName, mContext.getUserId(),
+        ComponentName componentName = new ComponentName(PACKAGE_NAME, CLASS_NAME);
+        this.mService.mRemoteService = new MockRemoteRotationResolverService(mContext,
+                componentName, mContext.getUserId(),
                 /* idleUnbindTimeoutMs */60000L,
                 /* Lock */ new Object());
     }
 
     @Test
     public void testResolveRotation_callOnSuccess() {
-        doReturn(true).when(mSpyService).isServiceAvailableLocked();
-        mSpyService.mCurrentRequest = null;
+        mService.mCurrentRequest = null;
 
         RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
                 Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
 
-        mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+        mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
                 "", 1000L, mCancellationSignal);
         verify(callbackInternal).onSuccess(anyInt());
     }
 
     @Test
     public void testResolveRotation_noCrashWhenCancelled() {
-        doReturn(true).when(mSpyService).isServiceAvailableLocked();
-
         RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
                 Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
 
         final CancellationSignal cancellationSignal = new CancellationSignal();
-        mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+        mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
                 "", 1000L, cancellationSignal);
         cancellationSignal.cancel();
+    }
 
-        verify(mSpyService.mCurrentRequest).cancelInternal();
+    private ResolveInfo createTestingResolveInfo() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = CLASS_NAME;
+        resolveInfo.serviceInfo.permission = Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE;
+        return resolveInfo;
     }
 
     static class MockRemoteRotationResolverService extends RemoteRotationResolverService {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
index 72c40ea..014bfd2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
@@ -23,7 +23,7 @@
 import androidx.annotation.NonNull;
 
 /** Fake implementation of {@link Vibrator} for service tests. */
-public final class FakeVibrator extends Vibrator {
+final class FakeVibrator extends Vibrator {
 
     private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
     private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index f562c16..4634e12 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -33,7 +33,7 @@
  * Provides {@link VibratorController} with controlled vibrator hardware capabilities and
  * interactions.
  */
-public final class FakeVibratorControllerProvider {
+final class FakeVibratorControllerProvider {
 
     private static final int EFFECT_DURATION = 20;
 
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 e71c2f5..8c62b7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -67,9 +67,8 @@
     private static final String REASON = "some reason";
     private static final VibrationAttributes VIBRATION_ATTRIBUTES =
             new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationEffect EFFECT = VibrationEffect.createOneShot(100, 255);
     private static final CombinedVibrationEffect SYNCED_EFFECT =
-            CombinedVibrationEffect.createSynced(EFFECT);
+            CombinedVibrationEffect.createSynced(VibrationEffect.createOneShot(100, 255));
 
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
@@ -105,6 +104,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         mInputDeviceDelegate.onInputDeviceAdded(1);
 
@@ -118,6 +118,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1));
         updateInputDevices(new int[]{1});
@@ -132,6 +133,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         updateInputDevices(new int[]{1});
 
@@ -142,6 +144,7 @@
     @Test
     public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
 
@@ -153,6 +156,7 @@
     @Test
     public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
 
@@ -167,6 +171,7 @@
     @Test
     public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
 
@@ -181,6 +186,7 @@
     @Test
     public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
 
@@ -195,8 +201,10 @@
     @Test
     public void onInputDeviceRemoved_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
                 createInputDeviceWithoutVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
 
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -209,7 +217,9 @@
     @Test
     public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
 
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -223,6 +233,7 @@
     public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -240,14 +251,16 @@
     public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
         assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
                 UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
-        verify(mIInputManagerMock).vibrate(eq(1), same(EFFECT), any());
-        verify(mIInputManagerMock).vibrate(eq(2), same(EFFECT), any());
+        verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
+        verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
     }
 
     @Test
@@ -261,7 +274,9 @@
     public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 3ff8e76..1b7e1ca 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
 
@@ -709,6 +710,7 @@
         assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
     }
 
+    @LargeTest
     @Test
     public void vibrate_withWaveform_totalVibrationTimeRespected() {
         int totalDuration = 10_000; // 10s
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index da3d1d6..ba0a472 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.vibrator;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -73,9 +73,7 @@
 
 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;
+import com.android.server.LocalServices;
 
 import org.junit.After;
 import org.junit.Before;
@@ -533,28 +531,15 @@
         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));
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(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());
-        }
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(10, 10));
+        vibrate(service, effect, ALARM_ATTRS);
+        verify(mIInputManagerMock).vibrateCombined(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(),
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
new file mode 100644
index 0000000..aa87958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ec28baf..07475e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1686,6 +1686,11 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+
+        }
+
+        @Override
         protected void loadDefaultsFromConfig() {
             mDefaultComponents.addAll(mDefaults);
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index be489c3..5614aa2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@
                 "disabledMessage", 0, "disabledMessageResName",
                 null, null, 0, null, 0, 0,
                 0, "iconResName", "bitmapPath", null, 0,
-                null, null);
+                null, null, 0);
         return si;
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index b13f80b..80a046a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -25,14 +30,16 @@
 
 import android.app.INotificationManager;
 import android.content.ComponentName;
-import android.content.pm.VersionedPackage;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
 import android.service.notification.NotificationListenerFilter;
+import android.service.notification.NotificationListenerService;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -48,8 +55,6 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 public class NotificationListenersTest extends UiServiceTestCase {
 
@@ -81,7 +86,7 @@
 
     @Test
     public void testReadExtraTag() throws Exception {
-        String xml = "<req_listeners>"
+        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
                 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
                 + "<allowed types=\"7\" />"
                 + "</listener>"
@@ -89,13 +94,13 @@
                 + "<allowed types=\"4\" />"
                 + "<disallowed pkg=\"pkg1\" uid=\"243\"/>"
                 + "</listener>"
-                + "</req_listeners>";
+                + "</" + TAG_REQUESTED_LISTENERS + ">";
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(xml.getBytes())), null);
         parser.nextTag();
-        mListeners.readExtraTag("req_listeners", parser);
+        mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser);
 
         validateListenersFromXml();
     }
@@ -158,52 +163,60 @@
     }
 
     @Test
-    public void testOnUserUnlocked() {
+    public void testEnsureFilters_newServiceNoMetadata() {
+        ServiceInfo si = new ServiceInfo();
+        si.packageName = "new2";
+        si.name = "comp2";
+
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull();
+    }
+
+    @Test
+    public void testEnsureFilters_preExisting() {
         // one exists already, say from xml
         VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
+        ServiceInfo siOld = new ServiceInfo();
+        siOld.packageName = mCn2.getPackageName();
+        siOld.name = mCn2.getClassName();
 
-        // new service exists or backfilling on upgrade to S
+        mListeners.ensureFilters(siOld, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf);
+    }
+
+    @Test
+    public void testEnsureFilters_newServiceWithMetadata() {
         ServiceInfo si = new ServiceInfo();
-        si.permission = mListeners.getConfig().bindPermission;
         si.packageName = "new";
         si.name = "comp";
-        ResolveInfo ri = new ResolveInfo();
-        ri.serviceInfo = si;
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2");
 
-        // incorrect service
-        ServiceInfo si2 = new ServiceInfo();
-        ResolveInfo ri2 = new ResolveInfo();
-        ri2.serviceInfo = si2;
-        si2.packageName = "new2";
-        si2.name = "comp2";
-
-        List<ResolveInfo> ris = new ArrayList<>();
-        ris.add(ri);
-        ris.add(ri2);
-
-        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
-
-        mListeners.onUserUnlocked(0);
-
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
-                .isEqualTo(4);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
-                .getDisallowedPackages())
-                .contains(a1);
+        mListeners.ensureFilters(si, 0);
 
         assertThat(mListeners.getNotificationListenerFilter(
                 Pair.create(si.getComponentName(), 0)).getTypes())
-                .isEqualTo(15);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
-                .getDisallowedPackages())
-                .isEmpty();
+                .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING);
+    }
 
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
-                .isNull();
+    @Test
+    public void testEnsureFilters_newServiceWithEmptyMetadata() {
+        ServiceInfo si = new ServiceInfo();
+        si.packageName = "new";
+        si.name = "comp";
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "");
 
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(
+                Pair.create(si.getComponentName(), 0)).getTypes())
+                .isEqualTo(0);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 72b8439..aa1110c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -719,7 +719,7 @@
         final ActivityRecord activity = createActivityWithTask();
         assertTrue(activity.hasSavedState());
 
-        ActivityRecord.activityResumedLocked(activity.appToken);
+        ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */);
         assertFalse(activity.hasSavedState());
         assertNull(activity.getSavedState());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 5597be9..2686a24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -27,6 +27,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -118,6 +119,19 @@
     }
 
     @Test
+    public void testRegisterOrganizer_ignoreUntrustedDisplay() throws RemoteException {
+        doReturn(false).when(mDisplayContent).isTrusted();
+
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        List<DisplayAreaAppearedInfo> infos = mOrganizerController
+                .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList();
+
+        assertThat(infos).isEmpty();
+        verify(organizer, never()).onDisplayAreaAppeared(any(DisplayAreaInfo.class),
+                any(SurfaceControl.class));
+    }
+
+    @Test
     public void testCreateTaskDisplayArea_topBelowRoot() {
         final String newTdaName = "testTda";
         final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
@@ -186,13 +200,20 @@
     @Test
     public void testCreateTaskDisplayArea_invalidDisplayAndRoot() {
         final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+
         assertThrows(IllegalArgumentException.class, () ->
                 mOrganizerController.createTaskDisplayArea(
                         organizer, SystemServicesTestRule.sNextDisplayId + 1, FEATURE_ROOT,
                         "testTda"));
+
         assertThrows(IllegalArgumentException.class, () ->
                 mOrganizerController.createTaskDisplayArea(
                         organizer, DEFAULT_DISPLAY, FEATURE_ROOT - 1, "testTda"));
+
+        doReturn(false).when(mDisplayContent).isTrusted();
+        assertThrows(IllegalArgumentException.class, () ->
+                mOrganizerController.createTaskDisplayArea(
+                        organizer, DEFAULT_DISPLAY, FEATURE_ROOT, "testTda"));
     }
 
     @Test
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 89b962b..d4c956d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -43,11 +43,15 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -57,6 +61,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.IDisplayAreaOrganizer;
 
 import com.google.android.collect.Lists;
 
@@ -525,6 +530,42 @@
         assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isEqualTo(tda);
     }
 
+    @Test
+    public void testDisplayContentUpdateDisplayAreaOrganizers_onDisplayAreaAppeared() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+        spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+        when(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+                .getOrganizerByFeature(FEATURE_VENDOR_FIRST))
+                .thenReturn(mockDisplayAreaOrganizer);
+
+        mDisplayContent.addChild(displayArea, 0);
+        mDisplayContent.updateDisplayAreaOrganizers();
+
+        assertEquals(mockDisplayAreaOrganizer, displayArea.mOrganizer);
+        verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+                .onDisplayAreaAppeared(
+                        eq(mockDisplayAreaOrganizer),
+                        argThat(it -> it == displayArea && it.getSurfaceControl() != null));
+    }
+
+    @Test
+    public void testRemoveImmediately_onDisplayAreaVanished() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+        displayArea.mOrganizer = mockDisplayAreaOrganizer;
+        spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+        mDisplayContent.addChild(displayArea, 0);
+
+        displayArea.removeImmediately();
+
+        assertNull(displayArea.mOrganizer);
+        verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+                .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea);
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds) {
             super(wms, ANY, "half display area");
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 cc4d4ea..e843dd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -482,8 +482,7 @@
     public void testHandleActivitySizeCompatModeChanged() {
         setUpDisplaySizeWithApp(1000, 2000);
         doReturn(true).when(mTask).isOrganized();
-        ActivityRecord activity = mActivity;
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFitted();
 
@@ -499,12 +498,12 @@
 
         // Make the activity resizable again by restarting it
         clearInvocations(mTask);
-        activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        activity.mVisibleRequested = true;
-        activity.restartProcessIfVisible();
+        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
+        mActivity.mVisibleRequested = true;
+        mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
-        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);
 
         // Expect null token when switching to non-size-compat mode activity.
         verify(mTask).onSizeCompatActivityChanged();
@@ -515,6 +514,46 @@
     }
 
     @Test
+    public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
+        setUpDisplaySizeWithApp(1000, 2000);
+        doReturn(true).when(mTask).isOrganized();
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        assertFitted();
+
+        // Resize the display so that the activity exercises size-compat mode.
+        resizeDisplay(mTask.mDisplayContent, 1000, 2500);
+
+        // Expect the exact token when the activity is in size compatibility mode.
+        verify(mTask).onSizeCompatActivityChanged();
+        ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
+
+        assertEquals(mActivity.appToken, taskInfo.topActivityToken);
+        assertTrue(taskInfo.topActivityInSizeCompat);
+
+        // Create another Task to hold another size compat activity.
+        clearInvocations(mTask);
+        final Task secondTask = new TaskBuilder(mSupervisor).setDisplay(mTask.getDisplayContent())
+                .setCreateActivity(true).build();
+        final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
+        doReturn(true).when(secondTask).isOrganized();
+        secondActivity.setState(Task.ActivityState.RESUMED,
+                "testHandleActivitySizeCompatModeChanged");
+        prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Resize the display so that the activity exercises size-compat mode.
+        resizeDisplay(mTask.mDisplayContent, 1000, 3000);
+
+        // Expect the exact token when the activity is in size compatibility mode.
+        verify(secondTask).onSizeCompatActivityChanged();
+        verify(mTask, never()).onSizeCompatActivityChanged();
+        taskInfo = secondTask.getTaskInfo();
+
+        assertEquals(secondActivity.appToken, taskInfo.topActivityToken);
+        assertTrue(taskInfo.topActivityInSizeCompat);
+    }
+
+    @Test
     public void testShouldUseSizeCompatModeOnResizableTask() {
         setUpDisplaySizeWithApp(1000, 2500);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 77fca3d..9c16143 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -549,6 +549,9 @@
             public void removeStartingWindow(int taskId) { }
 
             @Override
+            public void copySplashScreenView(int taskId) { }
+
+            @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
             @Override
@@ -609,10 +612,10 @@
             public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
 
             }
-
             @Override
             public void removeStartingWindow(int taskId) { }
-
+            @Override
+            public void copySplashScreenView(int taskId) { }
             @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
@@ -685,7 +688,8 @@
 
             @Override
             public void removeStartingWindow(int taskId) { }
-
+            @Override
+            public void copySplashScreenView(int taskId) { }
             @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
@@ -829,6 +833,8 @@
         @Override
         public void removeStartingWindow(int taskId) { }
         @Override
+        public void copySplashScreenView(int taskId) { }
+        @Override
         public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) {
             mInfo = info;
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 83b30a9..c13d6b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1144,6 +1144,9 @@
         public void removeStartingWindow(int taskId) {
         }
         @Override
+        public void copySplashScreenView(int taskId) {
+        }
+        @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
         }
         @Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 21cf3e5..3f6162d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5351,11 +5351,28 @@
         public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED =
                 KEY_PREFIX + "suggestion_ssid_list_with_mac_randomization_disabled";
 
+        /**
+         * Avoid SoftAp in 5GHz if cellular is on unlicensed 5Ghz using License Assisted Access
+         * (LAA).
+         */
+        public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL =
+                KEY_PREFIX + "avoid_5ghz_softap_for_laa_bool";
+
+        /**
+         * Avoid Wifi Direct in 5GHz if cellular is on unlicensed 5Ghz using License Assisted
+         * Access (LAA).
+         */
+        public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL =
+                KEY_PREFIX + "avoid_5ghz_wifi_direct_for_laa_bool";
+
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putInt(KEY_HOTSPOT_MAX_CLIENT_COUNT, 0);
             defaults.putStringArray(KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED,
                     new String[0]);
+            defaults.putBoolean(KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, false);
+            defaults.putBoolean(KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, false);
 
             return defaults;
         }
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index eb04f69..ac9e681 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -76,11 +76,11 @@
                 Params(enableDisable = null, targetSdk = 29, result = false),
                 Params(enableDisable = null, targetSdk = 30, result = true),
 
-                Params(enableDisable = true, targetSdk = 29, result = true),
+                Params(enableDisable = true, targetSdk = 29, result = false),
                 Params(enableDisable = true, targetSdk = 30, result = true),
 
                 Params(enableDisable = false, targetSdk = 29, result = false),
-                Params(enableDisable = false, targetSdk = 30, result = false)
+                Params(enableDisable = false, targetSdk = 30, result = true)
         )
     }
 
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 401d87a..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -125,21 +125,11 @@
     }
 
     /**
-     * Test rollbacks of staged installs involving only apks with bad update.
-     * Trigger rollback phase.
-     */
-    @Test
-    public void testBadApkOnly_Phase3_Crash() throws Exception {
-        // One more crash to trigger rollback
-        RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
-    }
-
-    /**
      * Test rollbacks of staged installs involving only apks.
      * Confirm rollback phase.
      */
     @Test
-    public void testBadApkOnly_Phase4_VerifyRollback() throws Exception {
+    public void testBadApkOnly_Phase3_VerifyRollback() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
 
@@ -447,8 +437,10 @@
                 Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
                 Rollback.from(TestApp.A, 0).to(TestApp.A1));
 
-        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
-        RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT-1 times
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+        // Sleep for a while to make sure we don't trigger rollback
+        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
     }
 
     @Test
@@ -504,6 +496,45 @@
     }
 
     @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
+    }
+
+    @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        // Trigger rollback of test app.
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(5), false);
+
+        // The final crash that causes rollback will come from the host side.
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+    }
+
+    @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getRecentlyCommittedRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
+        assertThat(rollback).isStaged();
+        assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
+    }
+
+    @Test
     public void hasMainlineModule() throws Exception {
         String pkgName = getModuleMetadataPackageName();
         boolean existed =  InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 1d5730f..65fb7b6c 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -153,13 +153,14 @@
         getDevice().reboot();
         runPhase("testBadApkOnly_Phase2_VerifyInstall");
 
-        // Trigger rollback and wait for reboot to happen
-        runPhase("testBadApkOnly_Phase3_Crash");
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
         waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
 
         getDevice().waitForDeviceAvailable();
 
-        runPhase("testBadApkOnly_Phase4_VerifyRollback");
+        runPhase("testBadApkOnly_Phase3_VerifyRollback");
 
         assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
         assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
@@ -304,8 +305,10 @@
         getDevice().reboot();
         // Verify apex was installed and then crash the apk
         runPhase("testRollbackApexWithApkCrashing_Phase2_Crash");
-        // Wait for crash to trigger rollback
-        waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
+        waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
         getDevice().waitForDeviceAvailable();
         // Verify rollback occurred due to crash of apk-in-apex
         runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback");
@@ -551,6 +554,30 @@
         });
     }
 
+    /**
+     * Tests that packages are monitored across multiple reboots.
+     */
+    @Test
+    public void testWatchdogMonitorsAcrossReboots() throws Exception {
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install");
+
+        // The first reboot will make the rollback available.
+        // Information about which packages are monitored will be persisted to a file before the
+        // second reboot, and read from disk after the second reboot.
+        getDevice().reboot();
+        getDevice().reboot();
+
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall");
+
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
+        waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
+        getDevice().waitForDeviceAvailable();
+
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
+    }
+
     private void pushTestApex() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
@@ -631,6 +658,12 @@
         }
     }
 
+    private void startActivity(String packageName) throws Exception {
+        String cmd = "am start -S -a android.intent.action.MAIN "
+                + "-c android.intent.category.LAUNCHER " + packageName;
+        getDevice().executeShellCommand(cmd);
+    }
+
     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
         String pid = "";
         String lastPid = "invalid";
diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml
index 1b8fa67..1f22cae 100644
--- a/tests/StagedInstallTest/StagedInstallInternalTest.xml
+++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <configuration description="Runs the internal staged install tests">
-    <option name="test-suite-tag" value="StagedInstallTest" />
+    <option name="test-suite-tag" value="StagedInstallInternalTest" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="StagedInstallInternalTestApp.apk" />
diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING
index fa2a60b..cc31f2c 100644
--- a/tests/StagedInstallTest/TEST_MAPPING
+++ b/tests/StagedInstallTest/TEST_MAPPING
@@ -1,6 +1,16 @@
 {
   "presubmit-large": [
     {
+      "name": "StagedInstallInternalTest",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
       "name": "StagedInstallInternalTest"
     }
   ]
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 2201efd..8dc53ac 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.cts.install.lib.host.InstallUtilsHost;
+import android.platform.test.annotations.LargeTest;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
@@ -201,6 +202,7 @@
 
     // Test rollback-app command waits for staged sessions to be ready
     @Test
+    @LargeTest
     public void testAdbRollbackAppWaitsForStagedReady() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
index 6bed5a8..4c60ccf 100644
--- a/tests/net/AndroidManifest.xml
+++ b/tests/net/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.NETWORK_FACTORY" />
     <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
+    <uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index d232a507..fd29a95 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -40,7 +40,7 @@
 @SmallTest
 public class OemNetworkPreferencesTest {
 
-    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT;
+    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
     private static final String TEST_PACKAGE = "com.google.apps.contacts";
 
     private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder();
@@ -54,7 +54,7 @@
     @Test
     public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() {
         assertThrows(NullPointerException.class,
-                () -> mBuilder.removeNetworkPreference(null));
+                () -> mBuilder.clearNetworkPreference(null));
     }
 
     @Test
@@ -129,7 +129,7 @@
 
         assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
 
-        mBuilder.removeNetworkPreference(TEST_PACKAGE);
+        mBuilder.clearNetworkPreference(TEST_PACKAGE);
         networkPreferences = mBuilder.build().getNetworkPreferences();
 
         assertFalse(networkPreferences.containsKey(TEST_PACKAGE));
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index e1da3d0..dc9e587 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -17,7 +17,6 @@
 package com.android.server;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -85,7 +84,6 @@
         final String typeName = ConnectivityManager.getNetworkTypeName(type);
         mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
         mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
             case TRANSPORT_ETHERNET:
diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java
index 95a7942..c548e30 100644
--- a/tests/net/java/android/net/VpnManagerTest.java
+++ b/tests/net/java/android/net/VpnManagerTest.java
@@ -49,7 +49,7 @@
     private static final String IDENTITY_STRING = "Identity";
     private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
 
-    private IConnectivityManager mMockCs;
+    private IVpnManager mMockService;
     private VpnManager mVpnManager;
     private final MockContext mMockContext =
             new MockContext() {
@@ -61,24 +61,26 @@
 
     @Before
     public void setUp() throws Exception {
-        mMockCs = mock(IConnectivityManager.class);
-        mVpnManager = new VpnManager(mMockContext, mMockCs);
+        mMockService = mock(IVpnManager.class);
+        mVpnManager = new VpnManager(mMockContext, mMockService);
     }
 
     @Test
     public void testProvisionVpnProfilePreconsented() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(true);
 
         // Expect there to be no intent returned, as consent has already been granted.
         assertNull(mVpnManager.provisionVpnProfile(profile));
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testProvisionVpnProfileNeedsConsent() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(false);
 
         // Expect intent to be returned, as consent has not already been granted.
         final Intent intent = mVpnManager.provisionVpnProfile(profile);
@@ -88,25 +90,25 @@
                 ComponentName.unflattenFromString(
                         "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
         assertEquals(expectedComponentName, intent.getComponent());
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testDeleteProvisionedVpnProfile() throws Exception {
         mVpnManager.deleteProvisionedVpnProfile();
-        verify(mMockCs).deleteVpnProfile(eq(PKG_NAME));
+        verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStartProvisionedVpnProfile() throws Exception {
         mVpnManager.startProvisionedVpnProfile();
-        verify(mMockCs).startVpnProfile(eq(PKG_NAME));
+        verify(mMockService).startVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStopProvisionedVpnProfile() throws Exception {
         mVpnManager.stopProvisionedVpnProfile();
-        verify(mMockCs).stopVpnProfile(eq(PKG_NAME));
+        verify(mMockService).stopVpnProfile(eq(PKG_NAME));
     }
 
     private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
index 2fd5e38..866f38c 100644
--- a/tests/net/java/android/net/VpnTransportInfoTest.java
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import static com.android.testutils.ParcelUtils.assertParcelSane;
-import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -36,7 +35,6 @@
     public void testParceling() {
         VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
         assertParcelSane(v, 1 /* fieldCount */);
-        assertParcelingIsLossless(v);
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e639a36..2a693eb 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -200,6 +200,7 @@
 import android.net.RouteInfo;
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
@@ -376,6 +377,7 @@
 
     private MockContext mServiceContext;
     private HandlerThread mCsHandlerThread;
+    private HandlerThread mVMSHandlerThread;
     private ConnectivityService.Dependencies mDeps;
     private ConnectivityService mService;
     private WrappedConnectivityManager mCm;
@@ -390,6 +392,7 @@
     private TestNetIdManager mNetIdManager;
     private QosCallbackMockHelper mQosCallbackMockHelper;
     private QosCallbackTracker mQosCallbackTracker;
+    private VpnManagerService mVpnManagerService;
 
     // State variables required to emulate NetworkPolicyManagerService behaviour.
     private int mUidRules = RULE_NONE;
@@ -1262,24 +1265,55 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
-    private void mockVpn(int uid) {
-        synchronized (mService.mVpns) {
-            int userId = UserHandle.getUserId(uid);
-            mMockVpn = new MockVpn(userId);
-            // This has no effect unless the VPN is actually connected, because things like
-            // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
-            // netId, and check if that network is actually connected.
-            mService.mVpns.put(userId, mMockVpn);
-        }
+    private VpnManagerService makeVpnManagerService() {
+        final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
+            public int getCallingUid() {
+                return mDeps.getCallingUid();
+            }
+
+            public HandlerThread makeHandlerThread() {
+                return mVMSHandlerThread;
+            }
+
+            public KeyStore getKeyStore() {
+                return mKeyStore;
+            }
+
+            public INetd getNetd() {
+                return mMockNetd;
+            }
+
+            public INetworkManagementService getINetworkManagementService() {
+                return mNetworkManagementService;
+            }
+        };
+        return new VpnManagerService(mServiceContext, deps);
+    }
+
+    private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        final TransportInfo ti = nc.getTransportInfo();
+        assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti,
+                ti instanceof VpnTransportInfo);
+        assertEquals(type, ((VpnTransportInfo) ti).type);
+
     }
 
     private void processBroadcastForVpn(Intent intent) {
-        // The BroadcastReceiver for this broadcast checks it is being run on the handler thread.
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(intent));
+        mServiceContext.sendBroadcast(intent);
+        HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
         waitForIdle();
     }
 
+    private void mockVpn(int uid) {
+        synchronized (mVpnManagerService.mVpns) {
+            int userId = UserHandle.getUserId(uid);
+            mMockVpn = new MockVpn(userId);
+            // Every running user always has a Vpn in the mVpns array, even if no VPN is running.
+            mVpnManagerService.mVpns.put(userId, mMockVpn);
+        }
+    }
+
     private void mockUidNetworkingBlocked() {
         doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class)
                 .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules,
@@ -1394,6 +1428,7 @@
         FakeSettingsProvider.clearSettingsProvider();
         mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
                 new FakeSettingsProvider());
+        mServiceContext.setUseRegisteredHandlers(true);
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
@@ -1403,6 +1438,7 @@
         initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
 
         mCsHandlerThread = new HandlerThread("TestConnectivityService");
+        mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
         mDeps = makeDependencies();
         returnRealCallingUid();
         mService = new ConnectivityService(mServiceContext,
@@ -1425,6 +1461,8 @@
         // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
         mService.systemReadyInternal();
+        mVpnManagerService = makeVpnManagerService();
+        mVpnManagerService.systemReady();
         mockVpn(Process.myUid());
         mCm.bindProcessToNetwork(null);
         mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -1452,7 +1490,6 @@
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
         doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
         doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
-        doReturn(mKeyStore).when(deps).getKeyStore();
         doAnswer(inv -> {
             mPolicyTracker = new WrappedMultinetworkPolicyTracker(
                     inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -2717,10 +2754,6 @@
 
         NetworkCapabilities filter = new NetworkCapabilities();
         filter.addCapability(capability);
-        // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
-        // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
-        // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
-        filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -3873,6 +3906,24 @@
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
 
+    @Test
+    public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(false /* validated */);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        assertThrows(SecurityException.class,
+                () -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
+        callback.assertNoCallback();
+
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+        mCm.registerSystemDefaultNetworkCallback(callback, handler);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(callback);
+    }
+
     private void setCaptivePortalMode(int mode) {
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
@@ -4062,7 +4113,6 @@
         handlerThread.start();
         NetworkCapabilities filter = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .addCapability(NET_CAPABILITY_INTERNET);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter);
@@ -5966,7 +6016,6 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkDownstreamBandwidthKbps(10);
         final NetworkCapabilities wifiNc = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
@@ -5975,7 +6024,6 @@
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
                 .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkUpstreamBandwidthKbps(20);
         mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
         mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
@@ -6484,6 +6532,8 @@
         assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
     }
 
     private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
@@ -6523,6 +6573,7 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         // A VPN without underlying networks is not suspended.
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         final int userId = UserHandle.getUserId(Process.myUid());
         assertDefaultNetworkCapabilities(userId /* no networks */);
@@ -6686,6 +6737,7 @@
         // By default, VPN is set to track default network (i.e. its underlying networks is null).
         // In case of no default network, VPN is considered metered.
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Connect to Cell; Cell is the default network.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -6743,6 +6795,7 @@
         NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertNotNull("nc=" + nc, nc.getUids());
         assertEquals(nc.getUids(), uidRangesForUid(uid));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Set an underlying network and expect to see the VPN transports change.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6825,8 +6878,8 @@
 
         // Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */,
-                allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+                true /* lockdown */, allowList);
         waitForIdle();
         assertNull(mCm.getActiveNetworkForUid(uid));
         // This is arguably overspecified: a UID that is not running doesn't have an active network.
@@ -6856,7 +6909,8 @@
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
 
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+                allowList);
         waitForIdle();
     }
 
@@ -7232,7 +7286,8 @@
         final int uid = Process.myUid();
         final int userId = UserHandle.getUserId(uid);
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
 
         UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
@@ -7254,7 +7309,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown, expect to see the network unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7267,7 +7322,8 @@
 
         // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
         allowList.add(TEST_PACKAGE_NAME);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
@@ -7300,11 +7356,12 @@
 
         // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
         // Everything should now be blocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
         allowList.clear();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
@@ -7317,7 +7374,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown. Everything is unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7329,7 +7386,8 @@
 
         // Enable and disable an always-on VPN package without lockdown. Expect no changes.
         reset(mMockNetd);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */,
+                allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7340,7 +7398,7 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7352,7 +7410,8 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         // Enable lockdown and connect a VPN. The VPN is not blocked.
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7398,11 +7457,14 @@
         when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
     }
 
-    private void establishLegacyLockdownVpn() throws Exception {
+    private void establishLegacyLockdownVpn(Network underlying) throws Exception {
+        // The legacy lockdown VPN only supports userId 0, and must have an underlying network.
+        assertNotNull(underlying);
         mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
         // The legacy lockdown VPN only supports userId 0.
         final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
         mMockVpn.registerAgent(ranges);
+        mMockVpn.setUnderlyingNetworks(new Network[]{underlying});
         mMockVpn.connect(true);
     }
 
@@ -7410,6 +7472,9 @@
     public void testLegacyLockdownVpn() throws Exception {
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+        // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(
+                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7418,6 +7483,10 @@
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
+
         // Pretend lockdown VPN was configured.
         setupLegacyLockdownVpn();
 
@@ -7447,6 +7516,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7458,6 +7528,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
+                mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7467,6 +7539,7 @@
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         b1.expectBroadcast();
 
         // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
@@ -7476,6 +7549,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         b1.expectBroadcast();
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7498,9 +7572,10 @@
         mMockVpn.expectStartLegacyVpnRunner();
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         b1.expectBroadcast();
         b2.expectBroadcast();
@@ -7512,9 +7587,7 @@
         assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
         assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
-        VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo();
-        assertNotNull(ti);
-        assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type);
+        assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY);
 
         // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
         final LinkProperties wifiLp = new LinkProperties();
@@ -7542,11 +7615,10 @@
         // fact that a VPN is connected should only result in the VPN itself being unblocked, not
         // any other network. Bug in isUidBlockedByVpn?
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         callback.expectCallback(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // While the VPN is reconnecting on the new network, everything is blocked.
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7557,9 +7629,10 @@
         // The VPN comes up again on wifi.
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         b1.expectBroadcast();
         b2.expectBroadcast();
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7573,14 +7646,10 @@
         assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Nothing much happens since it's not the default network.
-        // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
-        // NetworkInfo is updated. This is probably a bug.
-        // TODO: consider fixing this.
-        b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
         mCellNetworkAgent.disconnect();
-        b1.expectBroadcast();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
 
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -7590,6 +7659,7 @@
         b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         b1.expectBroadcast();
         callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
         b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
@@ -7641,13 +7711,19 @@
             mWiFiNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
             callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
-            reset(mMockNetd);
+            // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
+            //  it.
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+                reset(mMockNetd);
+            }
 
             mCellNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
             callbackWithoutCap.assertNoCallback();
-            verify(mMockNetd).networkClearDefault();
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkClearDefault();
+            }
 
             mCm.unregisterNetworkCallback(callbackWithCap);
             mCm.unregisterNetworkCallback(callbackWithoutCap);
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 799bcc8..c86224a 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -33,6 +33,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.IpSecAlgorithm;
@@ -44,6 +45,7 @@
 import android.net.IpSecTunnelInterfaceResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.os.Binder;
 import android.os.INetworkManagementService;
@@ -53,6 +55,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.IpSecService.TunnelInterfaceRecord;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -109,6 +113,7 @@
     };
 
     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
+    ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
 
     MockContext mMockContext = new MockContext() {
         @Override
@@ -116,12 +121,22 @@
             switch(name) {
                 case Context.APP_OPS_SERVICE:
                     return mMockAppOps;
+                case Context.CONNECTIVITY_SERVICE:
+                    return mMockConnectivityMgr;
                 default:
                     return null;
             }
         }
 
         @Override
+        public String getSystemServiceName(Class<?> serviceClass) {
+            if (ConnectivityManager.class == serviceClass) {
+                return Context.CONNECTIVITY_SERVICE;
+            }
+            return null;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mMockPkgMgr;
         }
@@ -151,6 +166,10 @@
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
     private static final int REMOTE_ENCAP_PORT = 4500;
 
+    private static final String BLESSED_PACKAGE = "blessedPackage";
+    private static final String SYSTEM_PACKAGE = "systemPackage";
+    private static final String BAD_PACKAGE = "badPackage";
+
     public IpSecServiceParameterizedTest(
             String sourceAddr, String destAddr, String localInnerAddr, int family) {
         mSourceAddr = sourceAddr;
@@ -174,15 +193,15 @@
         when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
 
         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
-            .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
         // A system package will not be granted the app op, so this should fall back to
         // a permissions check, which should pass.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage")))
-            .thenReturn(AppOpsManager.MODE_DEFAULT);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
         // A mismatch between the package name and the UID will return MODE_IGNORED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage")))
-            .thenReturn(AppOpsManager.MODE_IGNORED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
     }
 
     //TODO: Add a test to verify SPI.
@@ -338,7 +357,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -352,7 +371,7 @@
         ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -370,14 +389,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -396,14 +415,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -417,12 +436,12 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         // Attempting to create transform a second time with the same SPIs should throw an error...
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -430,7 +449,7 @@
         // ... even if the transform is deleted
         mIpSecService.deleteTransform(createTransformResp.resourceId);
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -443,7 +462,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -467,7 +486,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         mIpSecService.deleteTransform(createTransformResp.resourceId);
 
         verify(mMockNetd, times(1))
@@ -515,7 +534,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -562,7 +581,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -592,7 +611,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -638,7 +657,7 @@
     @Test
     public void testCreateTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Check that we have stored the tracking object, and retrieve it
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
@@ -661,11 +680,11 @@
     @Test
     public void testDeleteTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
 
-        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
+        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
@@ -678,10 +697,73 @@
         }
     }
 
+    private Network createFakeUnderlyingNetwork(String interfaceName) {
+        final Network fakeNetwork = new Network(1000);
+        final LinkProperties fakeLp = new LinkProperties();
+        fakeLp.setInterfaceName(interfaceName);
+        when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
+        return fakeNetwork;
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterface() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        mIpSecService.setNetworkForTunnelInterface(
+                tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+        assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = new Network(1000);
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
+            fail("Expected an IllegalArgumentException for invalid resource ID.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+
+        final Network newFakeNetwork =
+                createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+            fail(
+                    "Expected an IllegalArgumentException because the underlying network is the"
+                            + " network being exposed by this tunnel.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
     @Test
     public void testTunnelInterfaceBinderDeath() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -718,9 +800,9 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -728,8 +810,8 @@
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -758,17 +840,17 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -790,7 +872,7 @@
 
     @Test
     public void testAddRemoveAddressFromTunnelInterface() throws Exception {
-        for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
+        for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
             IpSecTunnelInterfaceResponse createTunnelResp =
                     createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
             mIpSecService.addAddressToTunnelInterface(
@@ -816,7 +898,7 @@
     public void testAddTunnelFailsForBadPackageName() throws Exception {
         try {
             IpSecTunnelInterfaceResponse createTunnelResp =
-                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage");
+                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
             fail("Expected a SecurityException for badPackage.");
         } catch (SecurityException expected) {
         }
@@ -830,7 +912,7 @@
         try {
             String addr = Inet4Address.getLoopbackAddress().getHostAddress();
             mIpSecService.createTunnelInterface(
-                    addr, addr, new Network(0), new Binder(), "blessedPackage");
+                    addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
             fail("Expected UnsupportedOperationException for disabled feature");
         } catch (UnsupportedOperationException expected) {
         }
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 1102624..4a1f96d 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -42,6 +42,8 @@
 
     private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>();
 
+    private boolean mUseRegisteredHandlers;
+
     public abstract class FutureIntent extends FutureTask<Intent> {
         public FutureIntent() {
             super(
@@ -61,17 +63,24 @@
     public class BroadcastInterceptor extends FutureIntent {
         private final BroadcastReceiver mReceiver;
         private final IntentFilter mFilter;
+        private final Handler mHandler;
 
-        public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+        public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter,
+                Handler handler) {
             mReceiver = receiver;
             mFilter = filter;
+            mHandler = mUseRegisteredHandlers ? handler : null;
         }
 
         public boolean dispatchBroadcast(Intent intent) {
             if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
                 if (mReceiver != null) {
                     final Context context = BroadcastInterceptingContext.this;
-                    mReceiver.onReceive(context, intent);
+                    if (mHandler == null) {
+                        mReceiver.onReceive(context, intent);
+                    } else {
+                        mHandler.post(() -> mReceiver.onReceive(context, intent));
+                    }
                     return false;
                 } else {
                     set(intent);
@@ -116,25 +125,38 @@
     }
 
     public FutureIntent nextBroadcastIntent(IntentFilter filter) {
-        final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+        final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null);
         synchronized (mInterceptors) {
             mInterceptors.add(interceptor);
         }
         return interceptor;
     }
 
+    /**
+     * Whether to send broadcasts to registered handlers. By default, receivers are called
+     * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is
+     * instead called by a runnable posted to the Handler specified when the receiver was
+     * registered. This method applies only to future registrations, already-registered receivers
+     * are unaffected.
+     */
+    public void setUseRegisteredHandlers(boolean use) {
+        synchronized (mInterceptors) {
+            mUseRegisteredHandlers = use;
+        }
+    }
+
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        synchronized (mInterceptors) {
-            mInterceptors.add(new BroadcastInterceptor(receiver, filter));
-        }
-        return null;
+        return registerReceiver(receiver, filter, null, null);
     }
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
             String broadcastPermission, Handler scheduler) {
-        return registerReceiver(receiver, filter);
+        synchronized (mInterceptors) {
+            mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
+        }
+        return null;
     }
 
     @Override
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..faa8b23 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -81,6 +81,7 @@
 
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
         verify(mIkeSession, never()).close();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -170,6 +171,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -179,5 +181,6 @@
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d936183..760379d 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -61,6 +61,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).kill();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -73,6 +74,7 @@
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
         verify(mIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -92,6 +94,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -101,5 +104,6 @@
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 8643d8a..1a1787a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -16,12 +16,18 @@
 
 package com.android.server.vcn;
 
+import static android.net.IpSecManager.IpSecTunnelInterface;
+
+import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
+import android.net.IpSecManager;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -37,6 +43,11 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        final IpSecTunnelInterface tunnelIface =
+                mContext.getSystemService(IpSecManager.class)
+                        .createIpSecTunnelInterface(
+                                DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
+        mGatewayConnection.setTunnelInterface(tunnelIface);
         mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
         mTestLooper.dispatchAll();
     }
@@ -77,6 +88,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index d0fec55..b09f3a0 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.vcn;
 
-import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import androidx.test.filters.SmallTest;
@@ -28,8 +28,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
-
 /** Tests for VcnGatewayConnection.DisconnectedState */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -40,6 +38,9 @@
 
         mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
 
+        // ensure that mGatewayConnection has an underlying Network before entering
+        // DisconnectingState
+        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2);
         mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
         mTestLooper.dispatchAll();
     }
@@ -49,12 +50,22 @@
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
-        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verify(mMockIkeSession).close();
+        verify(mMockIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutExpired() throws Exception {
-        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        Runnable delayedEvent =
+                verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         verify(mMockIkeSession).kill();
@@ -67,5 +78,6 @@
 
         // Should do nothing; already tearing down.
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 3f2b47c..d37e92f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -29,10 +29,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
+    private long mFirstRetryInterval;
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
+        mFirstRetryInterval = mConfig.getRetryInterval()[0];
+
         mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
         mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState);
         mTestLooper.dispatchAll();
@@ -46,6 +50,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
@@ -56,6 +61,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */);
     }
 
     @Test
@@ -66,13 +72,23 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutElapsingTriggersRetry() throws Exception {
-        mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]);
+        final Runnable delayedEvent =
+                verifyRetryTimeoutAlarmAndGetCallback(
+                        mFirstRetryInterval, false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index bc6bee2..748c792 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -132,10 +132,32 @@
 
     @Test
     public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+        verifyWakeLockSetUp();
+
         final TelephonySubscriptionSnapshot updatedSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
         mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
 
         verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
+        verifyWakeLockAcquired();
+
+        mTestLooper.dispatchAll();
+
+        verifyWakeLockReleased();
+    }
+
+    @Test
+    public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
+
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+        verify(mDisconnectRequestAlarm).cancel();
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index d449eab..0b44e03 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -21,9 +21,14 @@
 import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -42,12 +47,15 @@
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 
+import com.android.internal.util.WakeupMessage;
 import com.android.server.IpSecService;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
 
 import org.junit.Before;
 import org.mockito.ArgumentCaptor;
@@ -55,6 +63,7 @@
 import java.net.InetAddress;
 import java.util.Collections;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 public class VcnGatewayConnectionTestBase {
     protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
@@ -68,6 +77,7 @@
     protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
     protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
     protected static final int TEST_SUB_ID = 5;
+    protected static final long ELAPSED_REAL_TIME = 123456789L;
     protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
     protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
             new UnderlyingNetworkRecord(
@@ -94,6 +104,10 @@
     @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
     @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull protected final VcnWakeLock mWakeLock;
+    @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
+    @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
+    @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
@@ -110,6 +124,10 @@
         mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
         mDeps = mock(VcnGatewayConnection.Dependencies.class);
         mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+        mWakeLock = mock(VcnWakeLock.class);
+        mTeardownTimeoutAlarm = mock(WakeupMessage.class);
+        mDisconnectRequestAlarm = mock(WakeupMessage.class);
+        mRetryTimeoutAlarm = mock(WakeupMessage.class);
 
         mIpSecSvc = mock(IpSecService.class);
         setupIpSecManager(mContext, mIpSecSvc);
@@ -125,6 +143,19 @@
         doReturn(mUnderlyingNetworkTracker)
                 .when(mDeps)
                 .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
+        doReturn(mWakeLock)
+                .when(mDeps)
+                .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+
+        setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
+        setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
+        setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
+
+        doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
+    }
+
+    private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
+        doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
     }
 
     @Before
@@ -166,4 +197,60 @@
         verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
         return (VcnChildSessionCallback) captor.getValue();
     }
+
+    protected void verifyWakeLockSetUp() {
+        verify(mDeps).newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockAcquired() {
+        verify(mWakeLock).acquire();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockReleased() {
+        verify(mWakeLock).release();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    private Runnable verifyWakeupMessageSetUpAndGetCallback(
+            @NonNull String tag,
+            @NonNull WakeupMessage msg,
+            long delayInMillis,
+            boolean expectCanceled) {
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture());
+
+        verify(mDeps, atLeastOnce()).getElapsedRealTime();
+        verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis);
+        verify(msg, expectCanceled ? times(1) : never()).cancel();
+
+        return runnableCaptor.getValue();
+    }
+
+    protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM,
+                mTeardownTimeoutAlarm,
+                TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.DISCONNECT_REQUEST_ALARM,
+                mDisconnectRequestAlarm,
+                TimeUnit.SECONDS.toMillis(
+                        VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyRetryTimeoutAlarmAndGetCallback(
+            long delayInMillis, boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.RETRY_TIMEOUT_ALARM,
+                mRetryTimeoutAlarm,
+                delayInMillis,
+                expectCanceled);
+    }
 }