Merge "Add key layouts for Xbox clones" into rvc-dev
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 11d3a68..566f4cd 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -1114,20 +1114,22 @@
     static {
         // Using a LinkedHashMap to keep the insertion order when iterating over the keys.
         LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>();
-        extractorFactoriesByName.put("exo.Ac3Parser", Ac3Extractor::new);
-        extractorFactoriesByName.put("exo.Ac4Parser", Ac4Extractor::new);
-        extractorFactoriesByName.put("exo.AdtsParser", AdtsExtractor::new);
-        extractorFactoriesByName.put("exo.AmrParser", AmrExtractor::new);
-        extractorFactoriesByName.put("exo.FlacParser", FlacExtractor::new);
-        extractorFactoriesByName.put("exo.FlvParser", FlvExtractor::new);
-        extractorFactoriesByName.put("exo.FragmentedMp4Parser", FragmentedMp4Extractor::new);
+        // Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering,
+        // which in turn aims to minimize the chances of incorrect extractor selections.
         extractorFactoriesByName.put("exo.MatroskaParser", MatroskaExtractor::new);
-        extractorFactoriesByName.put("exo.Mp3Parser", Mp3Extractor::new);
+        extractorFactoriesByName.put("exo.FragmentedMp4Parser", FragmentedMp4Extractor::new);
         extractorFactoriesByName.put("exo.Mp4Parser", Mp4Extractor::new);
+        extractorFactoriesByName.put("exo.Mp3Parser", Mp3Extractor::new);
+        extractorFactoriesByName.put("exo.AdtsParser", AdtsExtractor::new);
+        extractorFactoriesByName.put("exo.Ac3Parser", Ac3Extractor::new);
+        extractorFactoriesByName.put("exo.TsParser", TsExtractor::new);
+        extractorFactoriesByName.put("exo.FlvParser", FlvExtractor::new);
         extractorFactoriesByName.put("exo.OggParser", OggExtractor::new);
         extractorFactoriesByName.put("exo.PsParser", PsExtractor::new);
-        extractorFactoriesByName.put("exo.TsParser", TsExtractor::new);
         extractorFactoriesByName.put("exo.WavParser", WavExtractor::new);
+        extractorFactoriesByName.put("exo.AmrParser", AmrExtractor::new);
+        extractorFactoriesByName.put("exo.Ac4Parser", Ac4Extractor::new);
+        extractorFactoriesByName.put("exo.FlacParser", FlacExtractor::new);
         EXTRACTOR_FACTORIES_BY_NAME = Collections.unmodifiableMap(extractorFactoriesByName);
 
         HashMap<String, Class> expectedTypeByParameterName = new HashMap<>();
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
index 205ffc2..30a8b45 100644
--- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
@@ -246,7 +246,7 @@
                     permissionState.isGranted() && (permissionState.getFlags()
                             & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0));
             serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString(
-                    permissionState.getFlags() & ~PackageManager.FLAG_PERMISSION_ONE_TIME));
+                    permissionState.getFlags()));
             serializer.endTag(null, TAG_PERMISSION);
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 1227006..a2f2c06 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8189,6 +8189,7 @@
     field public static final String OPTION_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth";
     field public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
     field public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
+    field public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
   }
 
   public class AppWidgetProvider extends android.content.BroadcastReceiver {
@@ -29356,9 +29357,9 @@
   public abstract class TvInputService extends android.app.Service {
     ctor public TvInputService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String);
+    method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String);
     method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String);
-    method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String);
+    method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(@NonNull String);
     method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String);
     field public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100; // 0x64
     field public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400; // 0x190
@@ -48146,6 +48147,7 @@
     method public String getMmsUserAgent();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getNai();
     method public String getNetworkCountryIso();
+    method @NonNull public String getNetworkCountryIso(int);
     method public String getNetworkOperator();
     method public String getNetworkOperatorName();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public int getNetworkSelectionMode();
diff --git a/api/system-current.txt b/api/system-current.txt
index f84b415..3b95f32 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11738,7 +11738,6 @@
     method public int getMaxNumberOfSimultaneouslyActiveSims();
     method public static long getMaxNumberVerificationTimeoutMillis();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getNetworkCountryIso(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4a9c4d6..0f8694f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3769,7 +3769,6 @@
     method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
     method public int getEmergencyNumberDbVersion();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
-    method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getNetworkCountryIso(int);
     method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 95de6c5..1e200c5 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -146,6 +146,8 @@
     struct uhid_event ev = {};
     ev.type = UHID_CREATE2;
     strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
+    std::string uniq = android::base::StringPrintf("Id: %d", id);
+    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
     memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
     ev.u.create2.rd_size = size;
     ev.u.create2.bus = bus;
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 2575542..f36b855 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -393,6 +393,8 @@
         WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
         AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"];
         SnapshotMergeReported snapshot_merge_reported = 255;
+        ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended =
+            256  [(module) = "framework"];
         SdkExtensionStatus sdk_extension_status = 354;
     }
 
@@ -3285,12 +3287,12 @@
     ];
 }
 
-/*
+/**
  * Logs foreground service starts and stops.
  * Note that this is not when a service starts or stops, but when it is
  * considered foreground.
  * Logged from
- *     //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
  */
 message ForegroundServiceStateChanged {
     optional int32 uid = 1 [(is_uid) = true];
@@ -3302,6 +3304,49 @@
         EXIT = 2;
     }
     optional State state = 3;
+
+    // Whether the fgs is allowed while-in-use permissions, i.e. is considered 'in-use' to the user.
+    // (If the fgs was started while the app wasn't TOP it usually will be denied these permissions)
+    optional bool allow_while_in_use_permission = 4;
+}
+
+/**
+ * Logs the number of times a uid accesses a sensitive AppOp during a foreground service session.
+ * A foreground service session is any continuous period during which the uid holds at least one
+ * foreground service; the atom will be pushed when the uid no longer holds any foreground services.
+ * Accesses initiated while the uid is in the TOP state are ignored.
+ * Sessions with no attempted accesses are not logged.
+ * Logged from
+ *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ForegroundServiceAppOpSessionEnded {
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The operation's name.
+    // To the extent possible, preserve the mapping from AppOpsManager.OP_ constants.
+    // Only these named ops are actually logged.
+    enum AppOpName {
+        OP_NONE = -1; // Also represents UNKNOWN.
+        OP_COARSE_LOCATION = 0;
+        OP_FINE_LOCATION = 1;
+        OP_CAMERA = 26;
+        OP_RECORD_AUDIO = 27;
+    }
+    optional AppOpName app_op_name = 2 [default = OP_NONE];
+
+    // The uid's permission mode for accessing the AppOp during this fgs session.
+    enum Mode {
+        MODE_UNKNOWN = 0;
+        MODE_ALLOWED = 1; // Always allowed
+        MODE_IGNORED = 2; // Denied
+        MODE_FOREGROUND = 3; // Allow-while-in-use (or allowed-one-time)
+    }
+    optional Mode app_op_mode = 3;
+
+    // Number of times this AppOp was requested and allowed.
+    optional int32 count_ops_accepted = 4;
+    // Number of times this AppOp was requested but denied.
+    optional int32 count_ops_rejected = 5;
 }
 
 /**
@@ -8189,7 +8234,7 @@
  */
 message CameraActionEvent {
     // Camera session duration
-    optional int64 duration = 1;
+    optional int64 duration_millis = 1;
 
     // Camera API level used
     optional int32 api_level = 2;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 3b0667d..316a018 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -61,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * This class describes an {@link AccessibilityService}. The system notifies an
@@ -552,6 +554,13 @@
      */
     private int mHtmlDescriptionRes;
 
+    // Used for html description of accessibility service. The <img> src tag must follow the
+    // prefix rule. e.g. <img src="R.drawable.fileName"/>
+    private static final String IMG_PREFIX = "R.drawable.";
+    private static final String ANCHOR_TAG = "a";
+    private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+            Collections.singletonList(ANCHOR_TAG));
+
     /**
      * Creates a new instance.
      */
@@ -782,12 +791,10 @@
     }
 
     /**
-     * The animated image resource id.
-     * <p>
-     *    <strong>Statically set from
-     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
-     * </p>
+     * Gets the animated image resource id.
+     *
      * @return The animated image resource id.
+     *
      * @hide
      */
     public int getAnimatedImageRes() {
@@ -797,10 +804,12 @@
     /**
      * The animated image drawable.
      * <p>
+     *    Image can not exceed the screen size.
      *    <strong>Statically set from
      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
      * </p>
-     * @return The animated image drawable.
+     * @return The animated image drawable, or null if the resource is invalid or the image
+     * exceed the screen size.
      */
     @Nullable
     public Drawable loadAnimatedImage(@NonNull Context context)  {
@@ -808,11 +817,8 @@
             return null;
         }
 
-        final PackageManager packageManager = context.getPackageManager();
-        final String packageName = mComponentName.getPackageName();
-        final ApplicationInfo applicationInfo = mResolveInfo.serviceInfo.applicationInfo;
-
-        return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
+        return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
+                mAnimatedImageRes);
     }
 
     /**
@@ -924,16 +930,17 @@
     }
 
     /**
-     * The localized html description of the accessibility service.
+     * The localized and restricted html description of the accessibility service.
      * <p>
+     *    Filters the <img> tag which do not meet the custom specification and the <a> tag.
      *    <strong>Statically set from
      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
      * </p>
-     * @return The localized html description.
+     * @return The localized and restricted html description.
      */
     @Nullable
     public String loadHtmlDescription(@NonNull PackageManager packageManager) {
-        if (mHtmlDescriptionRes == 0) {
+        if (mHtmlDescriptionRes == /* invalid */ 0) {
             return null;
         }
 
@@ -941,7 +948,7 @@
         final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
                 mHtmlDescriptionRes, serviceInfo.applicationInfo);
         if (htmlDescription != null) {
-            return htmlDescription.toString().trim();
+            return getFilteredHtmlText(htmlDescription.toString().trim());
         }
         return null;
     }
@@ -1414,4 +1421,103 @@
             return new AccessibilityServiceInfo[size];
         }
     };
+
+    /**
+     * Gets the filtered html string for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+     * the <img> tag which do not meet the custom specification and the <a> tag.
+     *
+     * @param text the target text is html format.
+     * @return the filtered html string.
+     *
+     * @hide
+     */
+    public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+        final String replacementStart = "<invalidtag ";
+        final String replacementEnd = "</invalidtag>";
+
+        for (String tag : UNSUPPORTED_TAG_LIST) {
+            final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+            final String regexEnd = "(?i)</" + tag + "\\s*>";
+            text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+            text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+        }
+
+        final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+        text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+                replacementStart);
+
+        return text;
+    }
+
+    /**
+     * Loads the animated image for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+     * whether to exceed the screen size.
+     *
+     * @param context the current context.
+     * @param applicationInfo the current application.
+     * @param resId the animated image resource id.
+     * @return the animated image which is safe.
+     *
+     * @hide
+     */
+    @Nullable
+    public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+            @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+        if (resId == /* invalid */ 0) {
+            return null;
+        }
+
+        final PackageManager packageManager = context.getPackageManager();
+        final String packageName = applicationInfo.packageName;
+        final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+                applicationInfo);
+        if (bannerDrawable == null) {
+            return null;
+        }
+
+        final boolean isImageWidthOverScreenLength =
+                bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+        final boolean isImageHeightOverScreenLength =
+                bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+        return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+                ? null
+                : bannerDrawable;
+    }
+
+    /**
+     * Gets the width of the screen.
+     *
+     * @param context the current context.
+     * @return the width of the screen in term of pixels.
+     *
+     * @hide
+     */
+    private static int getScreenWidthPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+                resources.getDisplayMetrics()));
+    }
+
+    /**
+     * Gets the height of the screen.
+     *
+     * @param context the current context.
+     * @return the height of the screen in term of pixels.
+     *
+     * @hide
+     */
+    private static int getScreenHeightPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+                resources.getDisplayMetrics()));
+    }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index 6209679..a812f29 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -28,13 +29,19 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.util.Xml;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * Activities of interest to users with accessibility needs may request to be targets of the
@@ -87,6 +94,13 @@
      */
     private final int mHtmlDescriptionRes;
 
+    // Used for html description of accessibility service. The <img> src tag must follow the
+    // prefix rule. e.g. <img src="R.drawable.fileName"/>
+    private static final String IMG_PREFIX = "R.drawable.";
+    private static final String ANCHOR_TAG = "a";
+    private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+            Collections.singletonList(ANCHOR_TAG));
+
     /**
      * Creates a new instance.
      *
@@ -134,7 +148,7 @@
             // Gets animated image
             mAnimatedImageRes = asAttributes.getResourceId(
                     com.android.internal.R.styleable
-                            .AccessibilityShortcutTarget_animatedImageDrawable, 0);
+                            .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
             // Gets html description
             mHtmlDescriptionRes = asAttributes.getResourceId(
                     com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
@@ -192,7 +206,7 @@
     }
 
     /**
-     * The animated image resource id of the accessibility shortcut target.
+     * Gets the animated image resource id.
      *
      * @return The animated image resource id.
      *
@@ -205,7 +219,8 @@
     /**
      * The animated image drawable of the accessibility shortcut target.
      *
-     * @return The animated image drawable.
+     * @return The animated image drawable, or null if the resource is invalid or the image
+     * exceed the screen size.
      */
     @Nullable
     public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -213,21 +228,20 @@
             return null;
         }
 
-        final PackageManager packageManager = context.getPackageManager();
-        final String packageName = mComponentName.getPackageName();
-        final ApplicationInfo applicationInfo = mActivityInfo.applicationInfo;
-
-        return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
+        return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
     }
 
     /**
-     * The localized html description of the accessibility shortcut target.
+     * The localized and restricted html description of the accessibility shortcut target.
+     * It filters the <img> tag which do not meet the custom specification and the <a> tag.
      *
-     * @return The localized html description.
+     * @return The localized and restricted html description.
      */
     @Nullable
     public String loadHtmlDescription(@NonNull PackageManager packageManager) {
-        return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes);
+        final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
+                mHtmlDescriptionRes);
+        return TextUtils.isEmpty(htmlDescription) ? null : getFilteredHtmlText(htmlDescription);
     }
 
     /**
@@ -291,4 +305,103 @@
         stringBuilder.append("]");
         return stringBuilder.toString();
     }
+
+    /**
+     * Gets the filtered html string for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+     * the <img> tag which do not meet the custom specification and the <a> tag.
+     *
+     * @param text the target text is html format.
+     * @return the filtered html string.
+     *
+     * @hide
+     */
+    public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+        final String replacementStart = "<invalidtag ";
+        final String replacementEnd = "</invalidtag>";
+
+        for (String tag : UNSUPPORTED_TAG_LIST) {
+            final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+            final String regexEnd = "(?i)</" + tag + "\\s*>";
+            text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+            text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+        }
+
+        final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+        text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+                replacementStart);
+
+        return text;
+    }
+
+    /**
+     * Loads the animated image for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+     * whether to exceed the screen size.
+     *
+     * @param context the current context.
+     * @param applicationInfo the current application.
+     * @param resId the animated image resource id.
+     * @return the animated image which is safe.
+     *
+     * @hide
+     */
+    @Nullable
+    public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+            @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+        if (resId == /* invalid */ 0) {
+            return null;
+        }
+
+        final PackageManager packageManager = context.getPackageManager();
+        final String packageName = applicationInfo.packageName;
+        final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+                applicationInfo);
+        if (bannerDrawable == null) {
+            return null;
+        }
+
+        final boolean isImageWidthOverScreenLength =
+                bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+        final boolean isImageHeightOverScreenLength =
+                bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+        return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+                ? null
+                : bannerDrawable;
+    }
+
+    /**
+     * Gets the width of the screen.
+     *
+     * @param context the current context.
+     * @return the width of the screen in term of pixels.
+     *
+     * @hide
+     */
+    private static int getScreenWidthPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+                resources.getDisplayMetrics()));
+    }
+
+    /**
+     * Gets the height of the screen.
+     *
+     * @param context the current context.
+     * @return the height of the screen in term of pixels.
+     *
+     * @hide
+     */
+    private static int getScreenHeightPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+                resources.getDisplayMetrics()));
+    }
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 82fdb90..b51bbdf 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2012,15 +2012,16 @@
         /** See {@link android.view.Surface.Rotation} */
         @Surface.Rotation
         private int mRotation;
+        /** The size of the snapshot before scaling */
+        private final Point mTaskSize;
         private final Rect mContentInsets;
-        // Whether this snapshot is a down-sampled version of the full resolution, used mainly for
-        // low-ram devices
+        // Whether this snapshot is a down-sampled version of the high resolution snapshot, used
+        // mainly for loading snapshots quickly from disk when user is flinging fast
         private final boolean mIsLowResolution;
         // Whether or not the snapshot is a real snapshot or an app-theme generated snapshot due to
         // the task having a secure window or having previews disabled
         private final boolean mIsRealSnapshot;
         private final int mWindowingMode;
-        private final float mScale;
         private final int mSystemUiVisibility;
         private final boolean mIsTranslucent;
         // Must be one of the named color spaces, otherwise, always use SRGB color space.
@@ -2028,9 +2029,9 @@
 
         public TaskSnapshot(long id,
                 @NonNull ComponentName topActivityComponent, GraphicBuffer snapshot,
-                @NonNull ColorSpace colorSpace, int orientation, int rotation, Rect contentInsets,
-                boolean isLowResolution, float scale, boolean isRealSnapshot, int windowingMode,
-                int systemUiVisibility, boolean isTranslucent) {
+                @NonNull ColorSpace colorSpace, int orientation, int rotation, Point taskSize,
+                Rect contentInsets, boolean isLowResolution, boolean isRealSnapshot,
+                int windowingMode, int systemUiVisibility, boolean isTranslucent) {
             mId = id;
             mTopActivityComponent = topActivityComponent;
             mSnapshot = snapshot;
@@ -2038,9 +2039,9 @@
                     ? ColorSpace.get(ColorSpace.Named.SRGB) : colorSpace;
             mOrientation = orientation;
             mRotation = rotation;
+            mTaskSize = new Point(taskSize);
             mContentInsets = new Rect(contentInsets);
             mIsLowResolution = isLowResolution;
-            mScale = scale;
             mIsRealSnapshot = isRealSnapshot;
             mWindowingMode = windowingMode;
             mSystemUiVisibility = systemUiVisibility;
@@ -2057,9 +2058,9 @@
                     : ColorSpace.get(ColorSpace.Named.SRGB);
             mOrientation = source.readInt();
             mRotation = source.readInt();
+            mTaskSize = source.readParcelable(null /* classLoader */);
             mContentInsets = source.readParcelable(null /* classLoader */);
             mIsLowResolution = source.readBoolean();
-            mScale = source.readFloat();
             mIsRealSnapshot = source.readBoolean();
             mWindowingMode = source.readInt();
             mSystemUiVisibility = source.readInt();
@@ -2111,6 +2112,14 @@
         }
 
         /**
+         * @return The size of the task at the point this snapshot was taken.
+         */
+        @UnsupportedAppUsage
+        public Point getTaskSize() {
+            return mTaskSize;
+        }
+
+        /**
          * @return The system/content insets on the snapshot. These can be clipped off in order to
          *         remove any areas behind system bars in the snapshot.
          */
@@ -2159,14 +2168,6 @@
             return mSystemUiVisibility;
         }
 
-        /**
-         * @return The scale this snapshot was taken in.
-         */
-        @UnsupportedAppUsage
-        public float getScale() {
-            return mScale;
-        }
-
         @Override
         public int describeContents() {
             return 0;
@@ -2180,9 +2181,9 @@
             dest.writeInt(mColorSpace.getId());
             dest.writeInt(mOrientation);
             dest.writeInt(mRotation);
+            dest.writeParcelable(mTaskSize, 0);
             dest.writeParcelable(mContentInsets, 0);
             dest.writeBoolean(mIsLowResolution);
-            dest.writeFloat(mScale);
             dest.writeBoolean(mIsRealSnapshot);
             dest.writeInt(mWindowingMode);
             dest.writeInt(mSystemUiVisibility);
@@ -2200,9 +2201,11 @@
                     + " mColorSpace=" + mColorSpace.toString()
                     + " mOrientation=" + mOrientation
                     + " mRotation=" + mRotation
+                    + " mTaskSize=" + mTaskSize.toString()
                     + " mContentInsets=" + mContentInsets.toShortString()
-                    + " mIsLowResolution=" + mIsLowResolution + " mScale=" + mScale
-                    + " mIsRealSnapshot=" + mIsRealSnapshot + " mWindowingMode=" + mWindowingMode
+                    + " mIsLowResolution=" + mIsLowResolution
+                    + " mIsRealSnapshot=" + mIsRealSnapshot
+                    + " mWindowingMode=" + mWindowingMode
                     + " mSystemUiVisibility=" + mSystemUiVisibility
                     + " mIsTranslucent=" + mIsTranslucent;
         }
@@ -2224,9 +2227,8 @@
             private ColorSpace mColorSpace;
             private int mOrientation;
             private int mRotation;
+            private Point mTaskSize;
             private Rect mContentInsets;
-            private boolean mIsLowResolution;
-            private float mScaleFraction;
             private boolean mIsRealSnapshot;
             private int mWindowingMode;
             private int mSystemUiVisibility;
@@ -2263,28 +2265,19 @@
                 return this;
             }
 
+            /**
+             * Sets the original size of the task
+             */
+            public Builder setTaskSize(Point size) {
+                mTaskSize = size;
+                return this;
+            }
+
             public Builder setContentInsets(Rect contentInsets) {
                 mContentInsets = contentInsets;
                 return this;
             }
 
-            /**
-             * Set to true if this is a low-resolution snapshot stored in *_reduced.jpg.
-             */
-            public Builder setIsLowResolution(boolean isLowResolution) {
-                mIsLowResolution = isLowResolution;
-                return this;
-            }
-
-            public float getScaleFraction() {
-                return mScaleFraction;
-            }
-
-            public Builder setScaleFraction(float scaleFraction) {
-                mScaleFraction = scaleFraction;
-                return this;
-            }
-
             public Builder setIsRealSnapshot(boolean realSnapshot) {
                 mIsRealSnapshot = realSnapshot;
                 return this;
@@ -2322,9 +2315,12 @@
                         mColorSpace,
                         mOrientation,
                         mRotation,
+                        mTaskSize,
                         mContentInsets,
-                        mIsLowResolution,
-                        mScaleFraction,
+                        // When building a TaskSnapshot with the Builder class, isLowResolution
+                        // is always false. Low-res snapshots are only created when loading from
+                        // disk.
+                        false /* isLowResolution */,
                         mIsRealSnapshot,
                         mWindowingMode,
                         mSystemUiVisibility,
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 8f02f15..a53fc35 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6910,11 +6910,7 @@
      * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
      */
     public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) {
-        try {
-            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return unsafeCheckOpRawNoThrow(op, uid, packageName);
     }
 
     /**
@@ -6923,8 +6919,17 @@
      * {@link #MODE_FOREGROUND}.
      */
     public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return unsafeCheckOpRawNoThrow(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Returns the <em>raw</em> mode associated with the op.
+     * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
+     * @hide
+     */
+    public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) {
         try {
-            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
+            return mService.checkOperationRaw(op, uid, packageName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index f0eedf3..7f43640 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -260,6 +260,7 @@
                 Log.e(TAG, "Failed to notify app target event", e);
                 e.rethrowAsRuntimeException();
             }
+            mRegisteredCallbacks.clear();
         } else {
             throw new IllegalStateException("This client has already been destroyed.");
         }
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6dea1c6..ccd8199 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -182,6 +182,16 @@
     public static final String EXTRA_APPWIDGET_ID = "appWidgetId";
 
     /**
+     * A bundle extra that contains whether or not an app has finished restoring a widget.
+     * <p> After restore, the app should set OPTION_APPWIDGET_RESTORE_COMPLETED to true on its
+     * widgets followed by calling {@link #updateAppWidget} to update the views.
+     *
+     * @see #updateAppWidgetOptions(int, Bundle)
+     */
+    public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
+
+
+    /**
      * A bundle extra that contains the lower bound on the current width, in dips, of a widget instance.
      */
     public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index ab91edf..a5d2198 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -200,6 +200,9 @@
      * provider can immediately generate new RemoteViews suitable for its newly-restored set
      * of instances.
      *
+     * <p>In addition, you should set {@link AppWidgetManager#OPTION_APPWIDGET_RESTORE_COMPLETED}
+     * to true indicate if a widget has been restored successfully from the provider's side.
+     *
      * {@more}
      *
      * @param context
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 9e40f46..9da0f20 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -54,7 +54,6 @@
 
     private final int mHash;
 
-    @UnsupportedAppUsage
     public ResourcesKey(@Nullable String resDir,
                         @Nullable String[] splitResDirs,
                         @Nullable String[] overlayDirs,
@@ -85,6 +84,18 @@
         mHash = hash;
     }
 
+    @UnsupportedAppUsage
+    public ResourcesKey(@Nullable String resDir,
+            @Nullable String[] splitResDirs,
+            @Nullable String[] overlayDirs,
+            @Nullable String[] libDirs,
+            int displayId,
+            @Nullable Configuration overrideConfig,
+            @Nullable CompatibilityInfo compatInfo) {
+        this(resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig, compatInfo,
+                null);
+    }
+
     public boolean hasOverrideConfiguration() {
         return !Configuration.EMPTY.equals(mOverrideConfiguration);
     }
diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS
new file mode 100644
index 0000000..2e94be5
--- /dev/null
+++ b/core/java/android/service/notification/OWNERS
@@ -0,0 +1,4 @@
+juliacr@google.com
+beverlyt@google.com
+dsandler@android.com
+pixel@google.com
\ No newline at end of file
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 7b24ba9..9a555c16 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -32,7 +32,7 @@
  * <p>
  * This API is not as convenient or type safe as the standard protobuf
  * classes. If possible, the best recommended library is to use protobuf lite.
- * However, in environements (such as the Android platform itself), a
+ * However, in environments (such as the Android platform itself), a
  * more memory efficient version is necessary.
  *
  * <p>Each write method takes an ID code from the protoc generated classes
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 5f9c480..cb9e746 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -233,6 +233,21 @@
         return mSeq;
     }
 
+    /**
+     * Gets the ID of this event. This is generated when an event is created and preserved until its
+     * last stage. It won't change just because the event crosses process boundary, but should
+     * change when making a copy with modifications.
+     * <p>
+     * To avoid exposing app usage to other processes this ID is generated from a CSPRNG. Therefore
+     * there isn't 100% guarantee on the uniqueness of this ID, though the chance of ID collisions
+     * is considerably low. The rule of thumb is not to rely on the uniqueness for production logic,
+     * but a good source for tracking an event (e.g. logging and profiling).
+     *
+     * @return The ID of this event.
+     * @hide
+     */
+    public abstract int getId();
+
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 235f5e1..e249c77 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1265,6 +1265,7 @@
 
     private KeyEvent mNext;
 
+    private int mId;
     @UnsupportedAppUsage
     private int mDeviceId;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -1350,9 +1351,9 @@
 
     private static native String nativeKeyCodeToString(int keyCode);
     private static native int nativeKeyCodeFromString(String keyCode);
+    private static native int nativeNextId();
 
-    private KeyEvent() {
-    }
+    private KeyEvent() {}
 
     /**
      * Create a new key event.
@@ -1362,6 +1363,7 @@
      * @param code The key code.
      */
     public KeyEvent(int action, int code) {
+        mId = nativeNextId();
         mAction = action;
         mKeyCode = code;
         mRepeatCount = 0;
@@ -1383,6 +1385,7 @@
      */
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat) {
+        mId = nativeNextId();
         mDownTime = downTime;
         mEventTime = eventTime;
         mAction = action;
@@ -1407,6 +1410,7 @@
      */
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState) {
+        mId = nativeNextId();
         mDownTime = downTime;
         mEventTime = eventTime;
         mAction = action;
@@ -1435,6 +1439,7 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode) {
+        mId = nativeNextId();
         mDownTime = downTime;
         mEventTime = eventTime;
         mAction = action;
@@ -1465,6 +1470,7 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode, int flags) {
+        mId = nativeNextId();
         mDownTime = downTime;
         mEventTime = eventTime;
         mAction = action;
@@ -1497,6 +1503,7 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode, int flags, int source) {
+        mId = nativeNextId();
         mDownTime = downTime;
         mEventTime = eventTime;
         mAction = action;
@@ -1523,6 +1530,7 @@
      * @param flags The flags for this key event
      */
     public KeyEvent(long time, String characters, int deviceId, int flags) {
+        mId = nativeNextId();
         mDownTime = time;
         mEventTime = time;
         mCharacters = characters;
@@ -1539,6 +1547,7 @@
      * Make an exact copy of an existing key event.
      */
     public KeyEvent(KeyEvent origEvent) {
+        mId = origEvent.mId;
         mDownTime = origEvent.mDownTime;
         mEventTime = origEvent.mEventTime;
         mAction = origEvent.mAction;
@@ -1567,6 +1576,7 @@
      */
     @Deprecated
     public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
+        mId = nativeNextId();  // Not an exact copy so assign a new ID.
         mDownTime = origEvent.mDownTime;
         mEventTime = eventTime;
         mAction = origEvent.mAction;
@@ -1598,15 +1608,16 @@
     }
 
     /**
-     * Obtains a (potentially recycled) key event.
+     * Obtains a (potentially recycled) key event. Used by native code to create a Java object.
      *
      * @hide
      */
-    public static KeyEvent obtain(long downTime, long eventTime, int action,
+    public static KeyEvent obtain(int id, long downTime, long eventTime, int action,
             int code, int repeat, int metaState,
             int deviceId, int scancode, int flags, int source, int displayId, @Nullable byte[] hmac,
             String characters) {
         KeyEvent ev = obtain();
+        ev.mId = id;
         ev.mDownTime = downTime;
         ev.mEventTime = eventTime;
         ev.mAction = action;
@@ -1628,12 +1639,24 @@
      *
      * @hide
      */
+    public static KeyEvent obtain(long downTime, long eventTime, int action,
+            int code, int repeat, int metaState,
+            int deviceId, int scanCode, int flags, int source, int displayId, String characters) {
+        return obtain(nativeNextId(), downTime, eventTime, action, code, repeat, metaState,
+                deviceId, scanCode, flags, source, displayId, null /* hmac */, characters);
+    }
+
+    /**
+     * Obtains a (potentially recycled) key event.
+     *
+     * @hide
+     */
     @UnsupportedAppUsage
     public static KeyEvent obtain(long downTime, long eventTime, int action,
             int code, int repeat, int metaState,
             int deviceId, int scancode, int flags, int source, String characters) {
         return obtain(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode,
-                flags, source, INVALID_DISPLAY, null /* hmac */, characters);
+                flags, source, INVALID_DISPLAY, characters);
     }
 
     /**
@@ -1645,6 +1668,7 @@
      */
     public static KeyEvent obtain(KeyEvent other) {
         KeyEvent ev = obtain();
+        ev.mId = other.mId;
         ev.mDownTime = other.mDownTime;
         ev.mEventTime = other.mEventTime;
         ev.mAction = other.mAction;
@@ -1695,6 +1719,12 @@
         // Do nothing.
     }
 
+    /** @hide */
+    @Override
+    public int getId() {
+        return mId;
+    }
+
     /**
      * Create a new key event that is the same as the given one, but whose
      * event time and repeat count are replaced with the given value.
@@ -1723,6 +1753,7 @@
     public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
             int newRepeat, int newFlags) {
         KeyEvent ret = new KeyEvent(event);
+        ret.mId = nativeNextId();  // Not an exact copy so assign a new ID.
         ret.mEventTime = eventTime;
         ret.mRepeatCount = newRepeat;
         ret.mFlags = newFlags;
@@ -1736,6 +1767,7 @@
      * @param action The new action code of the event.
      */
     private KeyEvent(KeyEvent origEvent, int action) {
+        mId = nativeNextId();  // Not an exact copy so assign a new ID.
         mDownTime = origEvent.mDownTime;
         mEventTime = origEvent.mEventTime;
         mAction = action;
@@ -1772,6 +1804,7 @@
      */
     public static KeyEvent changeFlags(KeyEvent event, int flags) {
         event = new KeyEvent(event);
+        event.mId = nativeNextId();  // Not an exact copy so assign a new ID.
         event.mFlags = flags;
         return event;
     }
@@ -3095,6 +3128,7 @@
     }
 
     private KeyEvent(Parcel in) {
+        mId = in.readInt();
         mDeviceId = in.readInt();
         mSource = in.readInt();
         mDisplayId = in.readInt();
@@ -3114,6 +3148,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(PARCEL_TOKEN_KEY_EVENT);
 
+        out.writeInt(mId);
         out.writeInt(mDeviceId);
         out.writeInt(mSource);
         out.writeInt(mDisplayId);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0068476..19eff72 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1550,6 +1550,8 @@
     private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
             boolean keepHistory);
     @CriticalNative
+    private static native int nativeGetId(long nativePtr);
+    @CriticalNative
     private static native int nativeGetDeviceId(long nativePtr);
     @CriticalNative
     private static native int nativeGetSource(long nativePtr);
@@ -2024,6 +2026,12 @@
         }
     }
 
+    /** @hide */
+    @Override
+    public int getId() {
+        return nativeGetId(mNativePtr);
+    }
+
     /** {@inheritDoc} */
     @Override
     public final int getDeviceId() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4a093e6..918b5a3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5379,7 +5379,8 @@
             if (mTracePrefix == null) {
                 mTracePrefix = getClass().getSimpleName();
             }
-            Trace.traceBegin(traceTag, mTracePrefix + " seq#=" + q.mEvent.getSequenceNumber());
+            Trace.traceBegin(traceTag, mTracePrefix + " id=0x"
+                    + Integer.toHexString(q.mEvent.getId()));
         }
     }
 
@@ -7986,12 +7987,13 @@
 
     private void deliverInputEvent(QueuedInputEvent q) {
         Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
-                q.mEvent.getSequenceNumber());
+                q.mEvent.getId());
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x"
                     + Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano="
-                    + q.mEvent.getEventTimeNano() + " seq#=" + q.mEvent.getSequenceNumber());
+                    + q.mEvent.getEventTimeNano() + " id=0x"
+                    + Integer.toHexString(q.mEvent.getId()));
         }
         try {
             if (mInputEventConsistencyVerifier != null) {
@@ -8032,7 +8034,7 @@
 
     private void finishInputEvent(QueuedInputEvent q) {
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
-                q.mEvent.getSequenceNumber());
+                q.mEvent.getId());
 
         if (q.mReceiver != null) {
             boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 84acf9a..0a2b1d4 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -114,9 +114,9 @@
 
     uint32_t publishedSeq = mNextPublishedSeq++;
     status_t status =
-            mInputPublisher.publishKeyEvent(publishedSeq, event->getDeviceId(), event->getSource(),
-                                            event->getDisplayId(), event->getHmac(),
-                                            event->getAction(), event->getFlags(),
+            mInputPublisher.publishKeyEvent(publishedSeq, event->getId(), event->getDeviceId(),
+                                            event->getSource(), event->getDisplayId(),
+                                            event->getHmac(), event->getAction(), event->getFlags(),
                                             event->getKeyCode(), event->getScanCode(),
                                             event->getMetaState(), event->getRepeatCount(),
                                             event->getDownTime(), event->getEventTime());
@@ -138,12 +138,12 @@
     for (size_t i = 0; i <= event->getHistorySize(); i++) {
         publishedSeq = mNextPublishedSeq++;
         status_t status =
-                mInputPublisher.publishMotionEvent(publishedSeq, event->getDeviceId(),
-                                                   event->getSource(), event->getDisplayId(),
-                                                   event->getHmac(), event->getAction(),
-                                                   event->getActionButton(), event->getFlags(),
-                                                   event->getEdgeFlags(), event->getMetaState(),
-                                                   event->getButtonState(),
+                mInputPublisher.publishMotionEvent(publishedSeq, event->getId(),
+                                                   event->getDeviceId(), event->getSource(),
+                                                   event->getDisplayId(), event->getHmac(),
+                                                   event->getAction(), event->getActionButton(),
+                                                   event->getFlags(), event->getEdgeFlags(),
+                                                   event->getMetaState(), event->getButtonState(),
                                                    event->getClassification(), event->getXScale(),
                                                    event->getYScale(), event->getXOffset(),
                                                    event->getYOffset(), event->getXPrecision(),
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index bbe563e..54567e5 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -75,6 +75,7 @@
     jmethodID obtain;
     jmethodID recycle;
 
+    jfieldID mId;
     jfieldID mDeviceId;
     jfieldID mSource;
     jfieldID mDisplayId;
@@ -96,6 +97,7 @@
     ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event->getHmac());
     jobject eventObj =
             env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain,
+                                        event->getId(),
                                         nanoseconds_to_milliseconds(event->getDownTime()),
                                         nanoseconds_to_milliseconds(event->getEventTime()),
                                         event->getAction(), event->getKeyCode(),
@@ -114,6 +116,7 @@
 
 status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
         KeyEvent* event) {
+    jint id = env->GetIntField(eventObj, gKeyEventClassInfo.mId);
     jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
     jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
     jint displayId = env->GetIntField(eventObj, gKeyEventClassInfo.mDisplayId);
@@ -131,7 +134,7 @@
     jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime);
     jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
 
-    event->initialize(deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
+    event->initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
                       metaState, repeatCount, milliseconds_to_nanoseconds(downTime),
                       milliseconds_to_nanoseconds(eventTime));
     return OK;
@@ -159,14 +162,18 @@
     return KeyEvent::getKeyCodeFromLabel(keyLabel.c_str());
 }
 
+static jint android_view_KeyEvent_nativeNextId() {
+    return static_cast<jint>(InputEvent::nextId());
+}
 
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod g_methods[] = {
-    { "nativeKeyCodeToString", "(I)Ljava/lang/String;",
-        (void*)android_view_KeyEvent_nativeKeyCodeToString},
-    { "nativeKeyCodeFromString", "(Ljava/lang/String;)I",
-        (void*)android_view_KeyEvent_nativeKeyCodeFromString},
+        {"nativeKeyCodeToString", "(I)Ljava/lang/String;",
+         (void*)android_view_KeyEvent_nativeKeyCodeToString},
+        {"nativeKeyCodeFromString", "(Ljava/lang/String;)I",
+         (void*)android_view_KeyEvent_nativeKeyCodeFromString},
+        {"nativeNextId", "()I", (void*)android_view_KeyEvent_nativeNextId},
 };
 
 int register_android_view_KeyEvent(JNIEnv* env) {
@@ -175,10 +182,11 @@
 
     gKeyEventClassInfo.obtain =
             GetStaticMethodIDOrDie(env, gKeyEventClassInfo.clazz, "obtain",
-                                   "(JJIIIIIIIII[BLjava/lang/String;)Landroid/view/KeyEvent;");
+                                   "(IJJIIIIIIIII[BLjava/lang/String;)Landroid/view/KeyEvent;");
     gKeyEventClassInfo.recycle = GetMethodIDOrDie(env, gKeyEventClassInfo.clazz,
             "recycle", "()V");
 
+    gKeyEventClassInfo.mId = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mId", "I");
     gKeyEventClassInfo.mDeviceId = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mDeviceId", "I");
     gKeyEventClassInfo.mSource = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mSource", "I");
     gKeyEventClassInfo.mDisplayId = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mDisplayId",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 3335fb2..9816d71 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -369,9 +369,10 @@
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
-    event->initialize(deviceId, source, displayId, INVALID_HMAC, action, 0, flags, edgeFlags,
-                      metaState, buttonState, static_cast<MotionClassification>(classification),
-                      1 /*xScale*/, 1 /*yScale*/, xOffset, yOffset, xPrecision, yPrecision,
+    event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0,
+                      flags, edgeFlags, metaState, buttonState,
+                      static_cast<MotionClassification>(classification), 1 /*xScale*/, 1 /*yScale*/,
+                      xOffset, yOffset, xPrecision, yPrecision,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                       downTimeNanos, eventTimeNanos, pointerCount, pointerProperties,
                       rawPointerCoords);
@@ -592,6 +593,11 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
+static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+    return event->getId();
+}
+
 static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDeviceId();
@@ -790,6 +796,7 @@
         // --------------- @CriticalNative ------------------
 
         {"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy},
+        {"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId},
         {"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId},
         {"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource},
         {"nativeSetSource", "(JI)V", (void*)android_view_MotionEvent_nativeSetSource},
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index cd7173a..97350ef 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -36,4 +36,5 @@
     optional int32 minHeight = 7;
     optional int32 maxWidth = 8;
     optional int32 maxHeight = 9;
+    optional bool restoreCompleted = 10;
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 490892e..8023990 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2752,7 +2752,9 @@
 
     <!-- The amount to scale reduced scale snapshots for Overview and snapshot starting windows.
          Reduced scale snapshots are loaded before full screen snapshots to improve load times and
-         minimize the chance the user will see an empty task card. -->
+         minimize the chance the user will see an empty task card. If set to 0, reduced scale
+         snapshots are disabled, and snapshots will only be stored at config_highResTaskSnapshotScale
+         -->
     <item name="config_lowResTaskSnapshotScale" format="float" type="dimen">0.5</item>
 
     <!-- Feature flag to store TaskSnapshot in 16 bit pixel format to save memory. -->
@@ -3957,14 +3959,14 @@
     <!-- Component name for the default module metadata provider on this device -->
     <string name="config_defaultModuleMetadataProvider" translatable="false">com.android.modulemetadata</string>
 
-    <!-- This is the default launcher component to use on secondary displays that support system
-         decorations.
-         This launcher activity must support multiple instances and have corresponding launch mode
-         set in AndroidManifest.
+    <!-- This is the default launcher package with an activity to use on secondary displays that
+         support system decorations.
+         This launcher package must have an activity that supports multiple instances and has
+         corresponding launch mode set in AndroidManifest.
          {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
-    <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
+    <string name="config_secondaryHomePackage" translatable="false">com.android.launcher3</string>
 
-    <!-- Force secondary home launcher specified in config_secondaryHomeComponent always. If this is
+    <!-- Force secondary home launcher specified in config_secondaryHomePackage always. If this is
          not set, secondary home launcher can be replaced by user. -->
     <bool name ="config_useSystemProvidedLauncherForSecondary">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7690b94..3ed3a64e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3678,7 +3678,7 @@
   <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
 
   <!-- For Secondary Launcher -->
-  <java-symbol type="string" name="config_secondaryHomeComponent" />
+  <java-symbol type="string" name="config_secondaryHomePackage" />
   <java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" />
 
   <java-symbol type="string" name="battery_saver_notification_channel_name" />
diff --git a/core/tests/coretests/src/android/view/KeyEventTest.java b/core/tests/coretests/src/android/view/KeyEventTest.java
index 14999c7..623008e 100644
--- a/core/tests/coretests/src/android/view/KeyEventTest.java
+++ b/core/tests/coretests/src/android/view/KeyEventTest.java
@@ -19,6 +19,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import android.os.Parcel;
 
@@ -28,10 +29,14 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashSet;
+import java.util.Set;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class KeyEventTest {
 
+    private static final int ID = 0xabcdef;
     private static final int DOWN_TIME = 50;
     private static final long EVENT_TIME = 100;
     private static final int ACTION = KeyEvent.ACTION_DOWN;
@@ -45,6 +50,8 @@
     private static final byte[] HMAC = null;
     private static final String CHARACTERS = null;
 
+    private static final int ID_SOURCE_MASK = 0x3 << 30;
+
     @Test
     public void testObtain() {
         KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
@@ -75,8 +82,7 @@
     public void testObtainWithDisplayId() {
         final int displayId = 5;
         KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
-                METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, displayId, null /* hmac*/,
-                CHARACTERS);
+                METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, displayId, CHARACTERS);
         assertEquals(DOWN_TIME, keyEvent.getDownTime());
         assertEquals(EVENT_TIME, keyEvent.getEventTime());
         assertEquals(ACTION, keyEvent.getAction());
@@ -91,6 +97,52 @@
         assertEquals(CHARACTERS, keyEvent.getCharacters());
     }
 
+    /**
+     * Tests that it can generate 500 consecutive distinct numbers. This is a non-deterministic test
+     * but with 30 bits randomness the failure rate is roughly 4.52e-5, which is negligible enough.
+     * Probability formula: N * (N - 1) * ... * (N - n + 1) / N^n, where N = 2^30 and n = 500 for
+     * this test.
+     */
+    @Test
+    public void testObtainGeneratesUniqueId() {
+        Set<Integer> set = new HashSet<>();
+        for (int i = 0; i < 500; ++i) {
+            KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
+                    METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
+            assertFalse("Found duplicate ID in round " + i,
+                    set.contains(keyEvent.getId()));
+            set.add(keyEvent.getSequenceNumber());
+        }
+    }
+
+    @Test
+    public void testConstructorGeneratesUniqueId() {
+        Set<Integer> set = new HashSet<>();
+        for (int i = 0; i < 500; ++i) {
+            KeyEvent keyEvent = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT);
+            assertFalse("Found duplicate sequence number in round " + i,
+                    set.contains(keyEvent.getId()));
+            set.add(keyEvent.getSequenceNumber());
+        }
+    }
+
+    @Test
+    public void testObtainGeneratesIdWithRightSource() {
+        for (int i = 0; i < 500; ++i) {
+            KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
+                    METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS);
+            assertEquals(0x3 << 30, ID_SOURCE_MASK & keyEvent.getId());
+        }
+    }
+
+    @Test
+    public void testConstructorGeneratesIdWithRightSource() {
+        for (int i = 0; i < 500; ++i) {
+            KeyEvent keyEvent = new KeyEvent(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT);
+            assertEquals(0x3 << 30, ID_SOURCE_MASK & keyEvent.getId());
+        }
+    }
+
     @Test
     public void testParcelUnparcel() {
         KeyEvent key1 = createKey();
@@ -112,11 +164,12 @@
     }
 
     private static KeyEvent createKey() {
-        return KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
-                METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, HMAC, CHARACTERS);
+        return KeyEvent.obtain(ID, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
+                DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, HMAC, CHARACTERS);
     }
 
     private static void compareKeys(KeyEvent key1, KeyEvent key2) {
+        assertEquals(key1.getId(), key2.getId());
         assertEquals(key1.getDownTime(), key2.getDownTime());
         assertEquals(key1.getEventTime(), key2.getEventTime());
         assertEquals(key1.getAction(), key2.getAction());
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 9d09830..786ae89 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -23,6 +23,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
 import android.view.MotionEvent.PointerCoords;
@@ -34,9 +35,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashSet;
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MotionEventTest {
+    private static final int ID_SOURCE_MASK = 0x3 << 30;
 
     @Test
     public void testObtainWithDisplayId() {
@@ -138,4 +143,30 @@
         assertEquals(30, event.getXCursorPosition(), 0.1);
         assertEquals(50, event.getYCursorPosition(), 0.1);
     }
+
+    /**
+     * Tests that it can generate 500 consecutive distinct numbers. This is a non-deterministic test
+     * but with 30 bits randomness the failure rate is roughly 4.52e-5, which is negligible enough.
+     * Probability formula: N * (N - 1) * ... * (N - n + 1) / N^n, where N = 2^30 and n = 500 for
+     * this test.
+     */
+    @Test
+    public void testObtainGeneratesUniqueId() {
+        Set<Integer> set = new HashSet<>();
+        for (int i = 0; i < 500; ++i) {
+            final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                    ACTION_DOWN, 30 /* x */, 50 /* y */, 0 /* metaState */);
+            assertFalse("Found duplicate ID in round " + i, set.contains(event.getId()));
+            set.add(event.getSequenceNumber());
+        }
+    }
+
+    @Test
+    public void testObtainGeneratesIdWithRightSource() {
+        for (int i = 0; i < 500; ++i) {
+            final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                    ACTION_DOWN, 30 /* x */, 50 /* y */, 0 /* metaState */);
+            assertEquals(0x3 << 30, ID_SOURCE_MASK & event.getId());
+        }
+    }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 145fd8b..99605ad 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -697,6 +697,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-668956537": {
+      "message": "  THUMBNAIL %s: CREATE",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/SurfaceFreezer.java"
+    },
     "-666510420": {
       "message": "With display frozen, orientationChangeComplete=%b",
       "level": "VERBOSE",
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f2b4db1..f800f9e 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2531,7 +2531,7 @@
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException {
         synchronized(mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
             invalidateByteBuffer(mCachedInputBuffers, index);
@@ -2783,7 +2783,7 @@
             long presentationTimeUs,
             int flags) throws CryptoException {
         synchronized(mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
             invalidateByteBuffer(mCachedInputBuffers, index);
@@ -2818,7 +2818,7 @@
      */
     public final int dequeueInputBuffer(long timeoutUs) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
@@ -3534,7 +3534,7 @@
     public final int dequeueOutputBuffer(
             @NonNull BufferInfo info, long timeoutUs) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
@@ -3916,7 +3916,7 @@
     @NonNull
     public ByteBuffer[] getInputBuffers() {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
             if (mCachedInputBuffers == null) {
@@ -3952,7 +3952,7 @@
     @NonNull
     public ByteBuffer[] getOutputBuffers() {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
             if (mCachedOutputBuffers == null) {
@@ -3984,7 +3984,7 @@
     @Nullable
     public ByteBuffer getInputBuffer(int index) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
@@ -4018,7 +4018,7 @@
     @Nullable
     public Image getInputImage(int index) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
@@ -4052,7 +4052,7 @@
     @Nullable
     public ByteBuffer getOutputBuffer(int index) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
@@ -4085,7 +4085,7 @@
     @Nullable
     public Image getOutputImage(int index) {
         synchronized (mBufferLock) {
-            if (mBufferMode != BUFFER_MODE_LEGACY) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException();
             }
         }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index b579144..95199cc 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -240,7 +240,7 @@
      * @param inputId The ID of the TV input associated with the session.
      */
     @Nullable
-    public abstract Session onCreateSession(String inputId);
+    public abstract Session onCreateSession(@NonNull String inputId);
 
     /**
      * Returns a concrete implementation of {@link RecordingSession}.
@@ -251,7 +251,7 @@
      * @param inputId The ID of the TV input associated with the recording session.
      */
     @Nullable
-    public RecordingSession onCreateRecordingSession(String inputId) {
+    public RecordingSession onCreateRecordingSession(@NonNull String inputId) {
         return null;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 3f55cea..8bf48e59 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -68,6 +68,7 @@
 
     private WifiInfo mWifiInfo;
     public boolean enabled;
+    public boolean isCaptivePortal;
     public int state;
     public boolean connected;
     public String ssid;
@@ -155,9 +156,11 @@
     private void updateStatusLabel() {
         final NetworkCapabilities networkCapabilities
                 = mConnectivityManager.getNetworkCapabilities(mWifiManager.getCurrentNetwork());
+        isCaptivePortal = false;
         if (networkCapabilities != null) {
             if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
                 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required);
+                isCaptivePortal = true;
                 return;
             } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
                 statusLabel = mContext.getString(R.string.wifi_limited_connection);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 0f2ee6a..610165a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -268,6 +268,7 @@
                     Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                    Settings.Global.ENHANCED_CONNECTIVITY_ENABLED,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
diff --git a/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
index 7964609..015e9f9 100644
--- a/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true"
-          android:color="@color/notification_guts_priority_button_bg_stroke_color_selected" />
+          android:color="?android:attr/colorAccent" />
     <item android:color="@color/notification_guts_priority_button_bg_stroke_color" />
 </selector>
diff --git a/packages/SystemUI/res/color/notification_guts_priority_contents.xml b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
index 56c43f0..42f0189 100644
--- a/packages/SystemUI/res/color/notification_guts_priority_contents.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_selected="true"
-          android:color="@color/notification_guts_priority_button_content_color_selected" />
+          android:color="?android:attr/colorAccent" />
     <item android:color="@color/notification_guts_priority_button_content_color" />
 </selector>
diff --git a/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml
index 1730dce..2fe6c7b 100644
--- a/packages/SystemUI/res/drawable/notification_guts_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_guts_bg.xml
@@ -16,7 +16,7 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/notification_guts_bg_color" />
+    <solid android:color="@color/notification_material_background_color" />
     <!--The radius is 1dp smaller than the notification one, to avoid aliasing bugs on the corners -->
     <corners android:radius="1dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/rounded_user_switcher_bg.xml b/packages/SystemUI/res/drawable/rounded_user_switcher_bg.xml
new file mode 100644
index 0000000..e3d010e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_user_switcher_bg.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight">
+   <item>
+      <shape>
+         <solid android:color="@color/qs_user_detail_avatar_frame" />
+
+         <padding
+                 android:left="1dp"
+                 android:right="1dp"
+                 android:bottom="1dp"
+                 android:top="1dp" />
+
+         <corners android:radius="48dp" />
+      </shape>
+   </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml
new file mode 100644
index 0000000..1f38b1e
--- /dev/null
+++ b/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+
+<!-- LinearLayout -->
+<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:layout_marginEnd="8dp"
+        android:gravity="end|center_vertical"
+        android:clickable="true"
+        android:background="@drawable/rounded_user_switcher_bg"
+        sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+        sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
+    <TextView android:id="@+id/user_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="13dp"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+            />
+    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
+            android:layout_width="@dimen/framed_avatar_size"
+            android:layout_height="@dimen/framed_avatar_size"
+            android:contentDescription="@null"
+            sysui:badgeDiameter="18dp"
+            sysui:badgeMargin="1dp" />
+</com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/app_ops_info.xml b/packages/SystemUI/res/layout/app_ops_info.xml
index 82a0115..bfa252c 100644
--- a/packages/SystemUI/res/layout/app_ops_info.xml
+++ b/packages/SystemUI/res/layout/app_ops_info.xml
@@ -26,7 +26,7 @@
         android:orientation="vertical"
         android:paddingStart="@*android:dimen/notification_content_margin_start"
         android:paddingEnd="@*android:dimen/notification_content_margin_end"
-        android:background="@color/notification_guts_bg_color"
+        android:background="@color/notification_material_background_color"
         android:theme="@*android:style/Theme.DeviceDefault.Light">
 
     <!-- Package Info -->
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index fb38e1c..b1e5165 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -27,8 +27,6 @@
         android:gravity="center_vertical"
         android:clickable="true"
         android:background="@drawable/ripple_drawable"
-        android:clipChildren="false"
-        android:clipToPadding="false"
         sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
         sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
     <TextView android:id="@+id/user_name"
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 87de9d4..5d03eee 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -24,8 +24,7 @@
     android:clipChildren="false"
     android:clipToPadding="true"
     android:orientation="vertical"
-    android:paddingStart="@*android:dimen/notification_content_margin_start"
-    android:background="@color/notification_guts_bg_color">
+    android:paddingStart="@*android:dimen/notification_content_margin_start">
 
     <!-- Package Info -->
     <RelativeLayout
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index ffe2eee..c350ed2 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -21,7 +21,7 @@
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:clickable="true"
-    android:background="@color/notification_guts_bg_color"
+    android:background="@color/notification_material_background_color"
     android:theme="@style/Theme.SystemUI">
 
     <RelativeLayout
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 9a66e8b..9b0fe46 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -39,23 +39,16 @@
     <!-- The color of the ripples on the untinted notifications -->
     <color name="notification_ripple_untinted_color">#30ffffff</color>
 
-    <!-- The "inside" of a notification, reached via longpress -->
-    <color name="notification_guts_bg_color">@color/GM2_grey_900</color>
-
     <!-- The color of the text inside a notification -->
     <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
 
     <color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
     <color name="notification_guts_sub_text_color">@color/GM2_grey_300</color>
     <color name="notification_guts_header_text_color">@color/GM2_grey_200</color>
-    <color name="notification_guts_info_button_color">@color/GM2_blue_300</color>
     <color name="notification_guts_priority_button_content_color">@color/GM2_grey_500</color>
-    <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_300</color>
     <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
     <color name="notification_guts_priority_button_bg_fill_color_selected">@color/GM2_grey_800</color>
     <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_700</color>
-    <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_blue_300</color>
-
     <color name="notification_section_header_label_color">@color/GM2_grey_200</color>
     <color name="notification_section_clear_all_btn_color">@color/GM2_grey_500</color>
     <color name="notification_channel_dialog_separator">@color/GM2_grey_700</color>
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 23b64e3..b375364 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -22,4 +22,28 @@
     <style name="UserDetailView">
         <item name="numColumns">4</item>
     </style>
+
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
+        <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?attr/wallpaperTextColor</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
+        <item name="android:fontWeight">700</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+    </style>
+
+    <style name="TextAppearance.QS.UserSwitcher">
+        <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="TextAppearance.QS.UserSwitcher.Activated">
+        <item name="android:fontWeight">700</item>
+        <item name="android:textStyle">bold</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 43ceb4e..73e49ce 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -88,21 +88,16 @@
     <!-- The color of the text inside a notification -->
     <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_light</color>
 
-    <!-- The "inside" of a notification, reached via longpress -->
-    <color name="notification_guts_bg_color">@color/GM2_grey_50</color>
 
     <color name="notification_guts_link_icon_tint">@color/GM2_grey_700</color>
     <color name="notification_guts_sub_text_color">@color/GM2_grey_700</color>
     <color name="notification_guts_header_text_color">@color/GM2_grey_900</color>
     <color name="notification_silence_color">#FF32c1de</color>
     <color name="notification_alert_color">#FFF87B2B</color>
-    <color name="notification_guts_info_button_color">@color/GM2_blue_700</color>
     <color name="notification_guts_priority_button_content_color">@color/GM2_grey_700</color>
-    <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_700</color>
     <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
     <color name="notification_guts_priority_button_bg_fill_color_selected">#FFFFFF</color>
     <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_300</color>
-    <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_blue_600</color>
 
     <color name="notification_section_header_label_color">@color/GM2_grey_900</color>
     <color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 8de2df5..a49aedf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -464,6 +464,9 @@
     <!-- Allow dragging the PIP to a location to close it -->
     <bool name="config_pipEnableDismissDragToEdge">true</bool>
 
+    <!-- Alow PIP to resize to a slightly bigger state upon touch/showing the menu -->
+    <bool name="config_pipEnableResizeForMenu">true</bool>
+
     <!-- SystemUI Plugins that can be loaded on user builds. -->
     <string-array name="config_pluginWhitelist" translatable="false">
         <item>com.android.systemui</item>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 36c4526..d9b1452 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -514,7 +514,7 @@
     <style name="TextAppearance.NotificationInfo.Button">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@color/notification_guts_info_button_color</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:background">@drawable/btn_borderless_rect</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:focusable">true</item>
@@ -550,7 +550,7 @@
     <style name="TextAppearance.NotificationImportanceButton">
         <item name="android:textSize">@dimen/notification_importance_button_text</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
-        <item name="android:textColor">@color/notification_guts_priority_contents</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:gravity">center</item>
     </style>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
index 4474a49..eca6ebf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
@@ -62,7 +62,9 @@
         orientation = snapshot.getOrientation();
         rotation = snapshot.getRotation();
         reducedResolution = snapshot.isLowResolution();
-        scale = snapshot.getScale();
+        // TODO(b/149579527): Pass task size instead of computing scale.
+        // Assume width and height were scaled the same; compute scale only for width
+        scale = (float) thumbnail.getWidth() / snapshot.getTaskSize().x;
         isRealSnapshot = snapshot.isRealSnapshot();
         isTranslucent = snapshot.isTranslucent();
         windowingMode = snapshot.getWindowingMode();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 79a25b2..3b855db 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -67,6 +67,8 @@
 
     // Allow dragging the PIP to a location to close it
     private final boolean mEnableDismissDragToEdge;
+    // Allow PIP to resize to a slightly bigger state upon touch
+    private final boolean mEnableResize;
     private final Context mContext;
     private final IActivityManager mActivityManager;
     private final PipBoundsHandler mPipBoundsHandler;
@@ -188,6 +190,7 @@
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
 
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+        mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
 
         // Register the listener for input consumer touch events
         inputConsumerController.setInputListener(this::handleTouchEvent);
@@ -297,7 +300,7 @@
             } else {
                 final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP
                         * mContext.getResources().getDisplayMetrics().density;
-                final Rect toMovementBounds = mMenuState == MENU_STATE_FULL
+                final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu()
                         ? new Rect(expandedMovementBounds)
                         : new Rect(normalMovementBounds);
                 final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
@@ -691,11 +694,12 @@
     };
 
     /**
-     * Updates the current movement bounds based on whether the menu is currently visible.
+     * Updates the current movement bounds based on whether the menu is currently visible and
+     * resized.
      */
     private void updateMovementBounds(int menuState) {
         boolean isMenuExpanded = menuState == MENU_STATE_FULL;
-        mMovementBounds = isMenuExpanded
+        mMovementBounds = isMenuExpanded && willResizeMenu()
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
         mPipBoundsHandler.setMinEdgeSize(
@@ -715,8 +719,11 @@
      * @return whether the menu will resize as a part of showing the full menu.
      */
     private boolean willResizeMenu() {
-        return mExpandedBounds.width() != mNormalBounds.width() ||
-                mExpandedBounds.height() != mNormalBounds.height();
+        if (!mEnableResize) {
+            return false;
+        }
+        return mExpandedBounds.width() != mNormalBounds.width()
+                || mExpandedBounds.height() != mNormalBounds.height();
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 39bfd5a..f169501 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -206,7 +206,7 @@
             state.icon = ResourceIcon.get(cb.wifiSignalIconId);
             state.label = removeDoubleQuotes(cb.ssid);
         } else if (wifiNotConnected) {
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disconnected);
+            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
             state.label = r.getString(R.string.quick_settings_wifi_label);
         } else {
             state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 97755fc..004b56b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -807,7 +807,7 @@
         // TODO: Move inflation logic out of this call
         if (mIsChildInGroup != isChildInGroup) {
             mIsChildInGroup = isChildInGroup;
-            if (mIsLowPriority) {
+            if (!isRemoved() && mIsLowPriority) {
                 RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
                 params.setUseLowPriority(mIsLowPriority);
                 mRowContentBindStage.requestRebind(mEntry, null /* callback */);
@@ -1576,13 +1576,15 @@
         // TODO: Move inflation logic out of this call and remove this method
         if (mNeedsRedaction != needsRedaction) {
             mNeedsRedaction = needsRedaction;
-            RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
-            if (needsRedaction) {
-                params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
-            } else {
-                params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC);
+            if (!isRemoved()) {
+                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+                if (needsRedaction) {
+                    params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
+                } else {
+                    params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC);
+                }
+                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
             }
-            mRowContentBindStage.requestRebind(mEntry, null /* callback */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 60eda06..bab7840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -20,6 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS;
@@ -218,7 +219,7 @@
         // TODO: consider querying this earlier in the notification pipeline and passing it in
         LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
                 .setPackage(mPackageName)
-                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED)
                 .setShortcutIds(Arrays.asList(mConversationId));
         List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
         if (shortcuts != null && !shortcuts.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index d090404..7d532a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -56,8 +56,9 @@
 
     private int mHotspotState;
     private volatile int mNumConnectedDevices;
-    private volatile boolean mIsTetheringSupported;
-    private volatile boolean mHasTetherableWifiRegexs;
+    // Assume tethering is available until told otherwise
+    private volatile boolean mIsTetheringSupported = true;
+    private volatile boolean mHasTetherableWifiRegexs = true;
     private boolean mWaitingForTerminalState;
 
     private TetheringManager.TetheringEventCallback mTetheringCallback =
@@ -97,6 +98,15 @@
                 new HandlerExecutor(backgroundHandler), mTetheringCallback);
     }
 
+    /**
+     * Whether hotspot is currently supported.
+     *
+     * This will return {@code true} immediately on creation of the controller, but may be updated
+     * later. Callbacks from this controllers will notify if the state changes.
+     *
+     * @return {@code true} if hotspot is supported (or we haven't been told it's not)
+     * @see #addCallback
+     */
     @Override
     public boolean isHotspotSupported() {
         return mIsTetheringSupported && mHasTetherableWifiRegexs
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 8bd0f2c..7c96386 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -88,15 +88,16 @@
         boolean wifiVisible = mCurrentState.enabled
                 && ((mCurrentState.connected && mCurrentState.inetCondition == 1)
                     || !mHasMobileData || visibleWhenEnabled);
-        String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
+        String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null;
         boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
         String contentDescription = getTextIfExists(getContentDescription()).toString();
         if (mCurrentState.inetCondition == 0) {
             contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet));
         }
         IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
-        IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
-                contentDescription);
+        IconState qsIcon = new IconState(mCurrentState.connected,
+                mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected
+                        : getQsCurrentIconId(), contentDescription);
         callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
                 ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
                 wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index cd91f22..5771472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -140,13 +139,16 @@
     }
 
     @Test
-    public void testDefault_hotspotNotSupported() {
-        assertFalse(mController.isHotspotSupported());
+    public void testHotspotSupported_default() {
+        assertTrue(mController.isHotspotSupported());
     }
 
     @Test
     public void testHotspotSupported_rightConditions() {
         mTetheringCallbackCaptor.getValue().onTetheringSupported(true);
+
+        assertTrue(mController.isHotspotSupported());
+
         mTetheringCallbackCaptor.getValue()
                 .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps);
 
@@ -154,13 +156,21 @@
     }
 
     @Test
-    public void testHotspotSupported_callbackCalledOnChange() {
+    public void testHotspotSupported_callbackCalledOnChange_tetheringSupported() {
         mController.addCallback(mCallback1);
-        mTetheringCallbackCaptor.getValue().onTetheringSupported(true);
+        mTetheringCallbackCaptor.getValue().onTetheringSupported(false);
+
+        verify(mCallback1).onHotspotAvailabilityChanged(false);
+    }
+
+    @Test
+    public void testHotspotSupported_callbackCalledOnChange_tetherableInterfaces() {
+        when(mTetheringInterfaceRegexps.getTetherableWifiRegexs())
+                .thenReturn(Collections.emptyList());
+        mController.addCallback(mCallback1);
         mTetheringCallbackCaptor.getValue()
                 .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps);
 
-        verify(mCallback1).onHotspotAvailabilityChanged(true);
+        verify(mCallback1).onHotspotAvailabilityChanged(false);
     }
-
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 32da4c9..9c250c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -72,7 +72,7 @@
                     testSsid);
             setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
-                    null);
+                    testSsid);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 0c9130d0..e6b0440 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -86,6 +86,7 @@
         mContext.addMockService(comp, mKeyChainService);
 
         when(mUserManager.getUserInfo(anyInt())).thenReturn(new UserInfo());
+        when(mUserManager.isUserUnlocked(any())).thenReturn(true);
 
         when(mKeyChainService.getUserCaAliases())
                 .thenReturn(new StringParceledListSlice(new ArrayList<String>()));
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 3111ab7..0c37235 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -95,11 +95,7 @@
     // TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
     sdk_version: "core_platform",
     privileged: true,
-    // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
-    // explicitly.
     jni_libs: [
-        "liblog",
-        "libnativehelper_compat_libc++",
         "libtetherutilsjni",
     ],
     resource_dirs: [
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
index 789019c..2006fb3 100644
--- a/proto/src/task_snapshot.proto
+++ b/proto/src/task_snapshot.proto
@@ -32,7 +32,12 @@
      int32 system_ui_visibility = 8;
      bool is_translucent = 9;
      string top_activity_component = 10;
-     float scale = 11;
+     // deprecated because original width and height are stored now instead of the scale.
+     float legacy_scale = 11 [deprecated=true];
      int64 id = 12;
      int32 rotation = 13;
+     // The task width when the snapshot was taken
+     int32 task_width = 14;
+     // The task height when the snapshot was taken
+     int32 task_height = 15;
  }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a8a2791..ad21075 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -641,8 +641,8 @@
                     final SuspendDialogInfo dialogInfo =
                             mPackageManagerInternal.getSuspendedDialogInfo(providerPackage,
                                     suspendingPackage, providerUserId);
-                    // TODO(b/148035643): Send the original widget intent or ACTION_MAIN as an
-                    // IntentSender to SuspendedAppActivity.
+                    // onUnsuspend is null because we don't want to start any activity on
+                    // unsuspending from a suspended widget.
                     onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
                             providerPackage, suspendingPackage, dialogInfo, null, null,
                             providerUserId);
@@ -794,6 +794,8 @@
         proto.write(WidgetProto.PROVIDER_PACKAGE, widget.provider.id.componentName.getPackageName());
         proto.write(WidgetProto.PROVIDER_CLASS, widget.provider.id.componentName.getClassName());
         if (widget.options != null) {
+            proto.write(WidgetProto.RESTORE_COMPLETED,
+                    widget.options.getBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED));
             proto.write(WidgetProto.MIN_WIDTH,
                 widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 0));
             proto.write(WidgetProto.MIN_HEIGHT,
@@ -2509,7 +2511,8 @@
         out.endTag(null, "h");
     }
 
-    private static void serializeAppWidget(XmlSerializer out, Widget widget) throws IOException {
+    private static void serializeAppWidget(XmlSerializer out, Widget widget,
+            boolean saveRestoreCompleted) throws IOException {
         out.startTag(null, "g");
         out.attribute(null, "id", Integer.toHexString(widget.appWidgetId));
         out.attribute(null, "rid", Integer.toHexString(widget.restoredId));
@@ -2528,10 +2531,50 @@
             out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
             out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
                     AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
+            if (saveRestoreCompleted) {
+                boolean restoreCompleted = widget.options.getBoolean(
+                        AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED);
+                out.attribute(null, "restore_completed", Boolean.toString(restoreCompleted));
+            }
         }
         out.endTag(null, "g");
     }
 
+    private static Bundle parseWidgetIdOptions(XmlPullParser parser) {
+        Bundle options = new Bundle();
+        String restoreCompleted = parser.getAttributeValue(null, "restore_completed");
+        if (restoreCompleted != null) {
+            options.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED,
+                    Boolean.valueOf(restoreCompleted));
+        }
+        String minWidthString = parser.getAttributeValue(null, "min_width");
+        if (minWidthString != null) {
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+                    Integer.parseInt(minWidthString, 16));
+        }
+        String minHeightString = parser.getAttributeValue(null, "min_height");
+        if (minHeightString != null) {
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+                    Integer.parseInt(minHeightString, 16));
+        }
+        String maxWidthString = parser.getAttributeValue(null, "max_width");
+        if (maxWidthString != null) {
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+                    Integer.parseInt(maxWidthString, 16));
+        }
+        String maxHeightString = parser.getAttributeValue(null, "max_height");
+        if (maxHeightString != null) {
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+                    Integer.parseInt(maxHeightString, 16));
+        }
+        String categoryString = parser.getAttributeValue(null, "host_category");
+        if (categoryString != null) {
+            options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+                    Integer.parseInt(categoryString, 16));
+        }
+        return options;
+    }
+
     @Override
     public List<String> getWidgetParticipants(int userId) {
         return mBackupRestoreController.getWidgetParticipants(userId);
@@ -3064,7 +3107,7 @@
                 if (widget.host.getUserId() != userId) {
                     continue;
                 }
-                serializeAppWidget(out, widget);
+                serializeAppWidget(out, widget, true);
             }
 
             Iterator<Pair<Integer, String>> it = mPackagesWithBindWidgetPermission.iterator();
@@ -3203,34 +3246,7 @@
                         String restoredIdString = parser.getAttributeValue(null, "rid");
                         widget.restoredId = (restoredIdString == null) ? 0
                                 : Integer.parseInt(restoredIdString, 16);
-
-                        Bundle options = new Bundle();
-                        String minWidthString = parser.getAttributeValue(null, "min_width");
-                        if (minWidthString != null) {
-                            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
-                                    Integer.parseInt(minWidthString, 16));
-                        }
-                        String minHeightString = parser.getAttributeValue(null, "min_height");
-                        if (minHeightString != null) {
-                            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
-                                    Integer.parseInt(minHeightString, 16));
-                        }
-                        String maxWidthString = parser.getAttributeValue(null, "max_width");
-                        if (maxWidthString != null) {
-                            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
-                                    Integer.parseInt(maxWidthString, 16));
-                        }
-                        String maxHeightString = parser.getAttributeValue(null, "max_height");
-                        if (maxHeightString != null) {
-                            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
-                                    Integer.parseInt(maxHeightString, 16));
-                        }
-                        String categoryString = parser.getAttributeValue(null, "host_category");
-                        if (categoryString != null) {
-                            options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
-                                    Integer.parseInt(categoryString, 16));
-                        }
-                        widget.options = options;
+                        widget.options = parseWidgetIdOptions(parser);
 
                         final int hostTag = Integer.parseInt(parser.getAttributeValue(
                                 null, "h"), 16);
@@ -4382,7 +4398,7 @@
                         if (widget.host.isInPackageForUser(backedupPackage, userId)
                                 || (provider != null
                                 &&  provider.isInPackageForUser(backedupPackage, userId))) {
-                            serializeAppWidget(out, widget);
+                            serializeAppWidget(out, widget, false);
                         }
                     }
 
@@ -4815,36 +4831,6 @@
                     || widget.provider.getUserId() == userId);
         }
 
-        private Bundle parseWidgetIdOptions(XmlPullParser parser) {
-            Bundle options = new Bundle();
-            String minWidthString = parser.getAttributeValue(null, "min_width");
-            if (minWidthString != null) {
-                options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
-                        Integer.parseInt(minWidthString, 16));
-            }
-            String minHeightString = parser.getAttributeValue(null, "min_height");
-            if (minHeightString != null) {
-                options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
-                        Integer.parseInt(minHeightString, 16));
-            }
-            String maxWidthString = parser.getAttributeValue(null, "max_width");
-            if (maxWidthString != null) {
-                options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
-                        Integer.parseInt(maxWidthString, 16));
-            }
-            String maxHeightString = parser.getAttributeValue(null, "max_height");
-            if (maxHeightString != null) {
-                options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
-                        Integer.parseInt(maxHeightString, 16));
-            }
-            String categoryString = parser.getAttributeValue(null, "host_category");
-            if (categoryString != null) {
-                options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
-                        Integer.parseInt(categoryString, 16));
-            }
-            return options;
-        }
-
         private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
             int pending = 0;
             final int N = updates.size();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4da60b8..3180ceb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -99,6 +99,7 @@
         "android.hardware.vibrator-java",
         "app-compat-annotations",
         "framework-tethering-stubs",
+        "ike-stubs",
     ],
 
     required: [
@@ -126,7 +127,6 @@
         "android.hidl.manager-V1.2-java",
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
-        "ike-stubs",
     ],
 
     plugins: [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7f98c7f..211da0a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
@@ -36,6 +37,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -88,11 +90,13 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.webkit.WebViewZygote;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.ServiceState;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -177,6 +181,10 @@
     /** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */
     private ArrayList<ServiceRecord> mTmpCollectionResults = null;
 
+    /** Mapping from uid to their foreground service AppOpCallbacks (if they have one). */
+    @GuardedBy("mAm")
+    private final SparseArray<AppOpCallback> mFgsAppOpCallbacks = new SparseArray<>();
+
     /**
      * For keeping ActiveForegroundApps retaining state while the screen is off.
      */
@@ -685,8 +693,8 @@
 
         if (!r.mAllowWhileInUsePermissionInFgs) {
             r.mAllowWhileInUsePermissionInFgs =
-                    shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingUid,
-                            service, r, allowBackgroundActivityStarts);
+                    shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid,
+                            callingUid, service, r, allowBackgroundActivityStarts);
         }
 
         return cmp;
@@ -1455,7 +1463,9 @@
                                 null, true, false, "");
                         FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                                 r.appInfo.uid, r.shortInstanceName,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
+                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                                r.mAllowWhileInUsePermissionInFgs);
+                        registerAppOpCallbackLocked(r);
                         mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
                     }
                     r.postNotification();
@@ -1504,9 +1514,11 @@
                 mAm.mAppOpsService.finishOperation(
                         AppOpsManager.getToken(mAm.mAppOpsService),
                         AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
+                unregisterAppOpCallbackLocked(r);
                 FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                         r.appInfo.uid, r.shortInstanceName,
-                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
+                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                        r.mAllowWhileInUsePermissionInFgs);
                 mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
                 if (r.app != null) {
                     mAm.updateLruProcessLocked(r.app, false, null);
@@ -1527,6 +1539,207 @@
         }
     }
 
+    /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */
+    private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) {
+        if (r.app == null) {
+            return;
+        }
+        final int uid = r.appInfo.uid;
+        AppOpCallback callback = mFgsAppOpCallbacks.get(uid);
+        if (callback == null) {
+            callback = new AppOpCallback(r.app, mAm.getAppOpsManager());
+            mFgsAppOpCallbacks.put(uid, callback);
+        }
+        callback.registerLocked();
+    }
+
+    /** Unregisters a foreground service's AppOpCallback. */
+    private void unregisterAppOpCallbackLocked(@NonNull ServiceRecord r) {
+        final int uid = r.appInfo.uid;
+        final AppOpCallback callback = mFgsAppOpCallbacks.get(uid);
+        if (callback != null) {
+            callback.unregisterLocked();
+            if (callback.isObsoleteLocked()) {
+                mFgsAppOpCallbacks.remove(uid);
+            }
+        }
+    }
+
+    /**
+     * For monitoring when {@link #LOGGED_AP_OPS} AppOps occur by an app while it is holding
+     * at least one foreground service and is not also in the TOP state.
+     * Once the uid no longer holds any foreground services, this callback becomes stale
+     * (marked by {@link #isObsoleteLocked()}) and must no longer be used.
+     *
+     * Methods that end in Locked should only be called while the mAm lock is held.
+     */
+    private static final class AppOpCallback {
+        /** AppOps that should be logged if they occur during a foreground service. */
+        private static final int[] LOGGED_AP_OPS = new int[] {
+                AppOpsManager.OP_COARSE_LOCATION,
+                AppOpsManager.OP_FINE_LOCATION,
+                AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_CAMERA
+        };
+
+        private final ProcessRecord mProcessRecord;
+
+        /** Count of acceptances per appop (for LOGGED_AP_OPS) during this fgs session. */
+        @GuardedBy("mCounterLock")
+        private final SparseIntArray mAcceptedOps = new SparseIntArray();
+        /** Count of rejections per appop (for LOGGED_AP_OPS) during this fgs session. */
+        @GuardedBy("mCounterLock")
+        private final SparseIntArray mRejectedOps = new SparseIntArray();
+
+        /** Lock for the purposes of mAcceptedOps and mRejectedOps. */
+        private final Object mCounterLock = new Object();
+
+        /**
+         * AppOp Mode (e.g. {@link AppOpsManager#MODE_ALLOWED} per op.
+         * This currently cannot change without the process being killed, so they are constants.
+         */
+        private final SparseIntArray mAppOpModes = new SparseIntArray();
+
+        /**
+         * Number of foreground services currently associated with this AppOpCallback (i.e.
+         * currently held for this uid).
+         */
+        @GuardedBy("mAm")
+        private int mNumFgs = 0;
+
+        /**
+         * Indicates that this Object is stale and must not be used.
+         * Specifically, when mNumFgs decreases down to 0, the callbacks will be unregistered and
+         * this AppOpCallback is unusable.
+         */
+        @GuardedBy("mAm")
+        private boolean mDestroyed = false;
+
+        private final AppOpsManager mAppOpsManager;
+
+        AppOpCallback(@NonNull ProcessRecord r, @NonNull AppOpsManager appOpsManager) {
+            mProcessRecord = r;
+            mAppOpsManager = appOpsManager;
+            for (int op : LOGGED_AP_OPS) {
+                int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, r.uid, r.info.packageName);
+                mAppOpModes.put(op, mode);
+            }
+        }
+
+        private final AppOpsManager.OnOpNotedListener mOpNotedCallback =
+                new AppOpsManager.OnOpNotedListener() {
+                    @Override
+                    public void onOpNoted(int op, int uid, String pkgName, int result) {
+                        if (uid == mProcessRecord.uid && isNotTop()) {
+                            incrementOpCount(op, result == AppOpsManager.MODE_ALLOWED);
+                        }
+                    }
+        };
+
+        private final AppOpsManager.OnOpActiveChangedInternalListener mOpActiveCallback =
+                new AppOpsManager.OnOpActiveChangedInternalListener() {
+                    @Override
+                    public void onOpActiveChanged(int op, int uid, String pkgName, boolean active) {
+                        if (uid == mProcessRecord.uid && active && isNotTop()) {
+                            incrementOpCount(op, true);
+                        }
+                    }
+        };
+
+        private boolean isNotTop() {
+            return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
+        }
+
+        private void incrementOpCount(int op, boolean allowed) {
+            synchronized (mCounterLock) {
+                final SparseIntArray counter = allowed ? mAcceptedOps : mRejectedOps;
+                final int index = counter.indexOfKey(op);
+                if (index < 0) {
+                    counter.put(op, 1);
+                } else {
+                    counter.setValueAt(index, counter.valueAt(index) + 1);
+                }
+            }
+        }
+
+        void registerLocked() {
+            if (isObsoleteLocked()) {
+                Slog.wtf(TAG, "Trying to register on a stale AppOpCallback.");
+                return;
+            }
+            mNumFgs++;
+            if (mNumFgs == 1) {
+                mAppOpsManager.startWatchingNoted(LOGGED_AP_OPS, mOpNotedCallback);
+                mAppOpsManager.startWatchingActive(LOGGED_AP_OPS, mOpActiveCallback);
+            }
+        }
+
+        void unregisterLocked() {
+            mNumFgs--;
+            if (mNumFgs <= 0) {
+                mDestroyed = true;
+                logFinalValues();
+                mAppOpsManager.stopWatchingNoted(mOpNotedCallback);
+                mAppOpsManager.stopWatchingActive(mOpActiveCallback);
+            }
+        }
+
+        /**
+         * Indicates that all foreground services for this uid are now over and the callback is
+         * stale and must never be used again.
+         */
+        boolean isObsoleteLocked() {
+            return mDestroyed;
+        }
+
+        private void logFinalValues() {
+            synchronized (mCounterLock) {
+                for (int op : LOGGED_AP_OPS) {
+                    final int acceptances = mAcceptedOps.get(op);
+                    final int rejections = mRejectedOps.get(op);
+                    if (acceptances > 0 ||  rejections > 0) {
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED,
+                                mProcessRecord.uid, opToEnum(op),
+                                modeToEnum(mAppOpModes.get(op)),
+                                acceptances, rejections
+                        );
+                    }
+                }
+            }
+        }
+
+        /** Maps AppOp mode to atoms.proto enum. */
+        private static int modeToEnum(int mode) {
+            switch (mode) {
+                case AppOpsManager.MODE_ALLOWED: return FrameworkStatsLog
+                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_ALLOWED;
+                case AppOpsManager.MODE_IGNORED: return FrameworkStatsLog
+                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_IGNORED;
+                case AppOpsManager.MODE_FOREGROUND: return FrameworkStatsLog
+                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_FOREGROUND;
+                default: return FrameworkStatsLog
+                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_UNKNOWN;
+            }
+        }
+    }
+
+    /** Maps AppOp op value to atoms.proto enum. */
+    private static int opToEnum(int op) {
+        switch (op) {
+            case AppOpsManager.OP_COARSE_LOCATION: return FrameworkStatsLog
+                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_COARSE_LOCATION;
+            case AppOpsManager.OP_FINE_LOCATION: return FrameworkStatsLog
+                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_FINE_LOCATION;
+            case AppOpsManager.OP_CAMERA: return FrameworkStatsLog
+                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_CAMERA;
+            case AppOpsManager.OP_RECORD_AUDIO: return FrameworkStatsLog
+                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_RECORD_AUDIO;
+            default: return FrameworkStatsLog
+                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_NONE;
+        }
+    }
+
     private void cancelForegroundNotificationLocked(ServiceRecord r) {
         if (r.foregroundId != 0) {
             // First check to see if this app has any other active foreground services
@@ -1865,9 +2078,9 @@
             }
 
             if (!s.mAllowWhileInUsePermissionInFgs) {
-                final int callingUid = Binder.getCallingUid();
                 s.mAllowWhileInUsePermissionInFgs =
-                        shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingUid,
+                        shouldAllowWhileInUsePermissionInFgsLocked(callingPackage,
+                                Binder.getCallingPid(), Binder.getCallingUid(),
                                 service, s, false);
             }
 
@@ -3136,9 +3349,11 @@
             mAm.mAppOpsService.finishOperation(
                     AppOpsManager.getToken(mAm.mAppOpsService),
                     AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
+            unregisterAppOpCallbackLocked(r);
             FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                     r.appInfo.uid, r.shortInstanceName,
-                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
+                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+                    r.mAllowWhileInUsePermissionInFgs);
             mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
         }
 
@@ -4625,7 +4840,8 @@
      * @return true if allow, false otherwise.
      */
     private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage,
-            int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) {
+            int callingPid, int callingUid, Intent intent, ServiceRecord r,
+            boolean allowBackgroundActivityStarts) {
         // Is the background FGS start restriction turned on?
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
             return true;
@@ -4635,13 +4851,6 @@
             return true;
         }
 
-        // Is the service in a whitelist?
-        final boolean hasAllowBackgroundActivityStartsToken = r.app != null
-                ? r.app.mAllowBackgroundActivityStartsTokens.contains(r) : false;
-        if (hasAllowBackgroundActivityStartsToken) {
-            return true;
-        }
-
         boolean isCallerSystem = false;
         final int callingAppId = UserHandle.getAppId(callingUid);
         switch (callingAppId) {
@@ -4660,6 +4869,11 @@
             return true;
         }
 
+        if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
+                == PERMISSION_GRANTED) {
+            return true;
+        }
+
         // Is the calling UID at PROCESS_STATE_TOP or above?
         final boolean isCallingUidTopApp = appIsTopLocked(callingUid);
         if (isCallingUidTopApp) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 7cb8458..612fd39 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -178,6 +178,8 @@
             float maxRefreshRate = Float.POSITIVE_INFINITY;
             int lowestConsideredPriority = Vote.MIN_PRIORITY;
             while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
+                minRefreshRate = 0f;
+                maxRefreshRate = Float.POSITIVE_INFINITY;
                 int height = Vote.INVALID_SIZE;
                 int width = Vote.INVALID_SIZE;
 
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 63054cf..fd8e159 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -40,7 +40,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.Signature;
@@ -202,7 +201,7 @@
                                 intent,
                                 /* onFinished= */ null,
                                 /* handler= */ null);
-                    } catch (IntentSender.SendIntentException e) {
+                    } catch (Exception e) {
                         Slog.e(TAG, "Error sending status feedback.", e);
                     }
                 });
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c9c6d51..ceb1cd4 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -51,6 +51,7 @@
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_NOT_PERCEPTIBLE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
@@ -3448,16 +3449,10 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(onlyImportant);
             for (ConversationChannelWrapper conversation : conversations) {
-                LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
-                        .setPackage(conversation.getPkg())
-                        .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
-                        .setShortcutIds(Arrays.asList(
-                                conversation.getNotificationChannel().getConversationId()));
-                List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(
-                        query, UserHandle.of(UserHandle.getUserId(conversation.getUid())));
-                if (shortcuts != null && !shortcuts.isEmpty()) {
-                    conversation.setShortcutInfo(shortcuts.get(0));
-                }
+                conversation.setShortcutInfo(getShortcutInfo(
+                        conversation.getNotificationChannel().getConversationId(),
+                        conversation.getPkg(),
+                        UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -3477,16 +3472,10 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(pkg, uid);
             for (ConversationChannelWrapper conversation : conversations) {
-                LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
-                        .setPackage(pkg)
-                        .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
-                        .setShortcutIds(Arrays.asList(
-                                conversation.getNotificationChannel().getConversationId()));
-                List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(
-                        query, UserHandle.of(UserHandle.getUserId(uid)));
-                if (shortcuts != null && !shortcuts.isEmpty()) {
-                    conversation.setShortcutInfo(shortcuts.get(0));
-                }
+                conversation.setShortcutInfo(getShortcutInfo(
+                        conversation.getNotificationChannel().getConversationId(),
+                        pkg,
+                        UserHandle.of(UserHandle.getUserId(uid))));
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -5646,6 +5635,8 @@
             }
         }
 
+        r.setShortcutInfo(getShortcutInfo(notification.getShortcutId(), pkg, user));
+
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.getSbn().getOverrideGroupKey() != null)) {
             return;
@@ -5959,20 +5950,33 @@
         return false;
     }
 
+    private ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (shortcutId == null || packageName == null || user == null) {
+                return null;
+            }
+            LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+            if (packageName != null) {
+                query.setPackage(packageName);
+            }
+            if (shortcutId != null) {
+                query.setShortcutIds(Arrays.asList(shortcutId));
+            }
+            query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
+            List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
+            ShortcutInfo shortcutInfo = shortcuts != null && shortcuts.size() > 0
+                    ? shortcuts.get(0)
+                    : null;
+            return shortcutInfo;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private boolean hasValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
-        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
-        if (packageName != null) {
-            query.setPackage(packageName);
-        }
-        if (shortcutId != null) {
-            query.setShortcutIds(Arrays.asList(shortcutId));
-        }
-        query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED);
-        List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
-        ShortcutInfo shortcutInfo = shortcuts != null && shortcuts.size() > 0
-                ? shortcuts.get(0)
-                : null;
-        return shortcutInfo != null;
+        ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user);
+        return shortcutInfo != null && shortcutInfo.isLongLived();
     }
 
     private void logBubbleError(String key, String failureMessage) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f92e1fc..9d243e4 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -35,6 +35,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
@@ -166,6 +167,7 @@
     private boolean mAllowBubble;
     private Light mLight;
     private boolean mIsNotConversationOverride;
+    private ShortcutInfo mShortcutInfo;
     /**
      * This list contains system generated smart actions from NAS, app-generated smart actions are
      * stored in Notification.actions with isContextual() set to true.
@@ -1338,14 +1340,20 @@
         return hasCustomRemoteView && !hasDecoratedStyle;
     }
 
-    /** Whether this notification is a conversation notification. */
+    public void setShortcutInfo(ShortcutInfo shortcutInfo) {
+        mShortcutInfo = shortcutInfo;
+    }
+
+    /**
+     * Whether this notification is a conversation notification.
+     */
     public boolean isConversation() {
         Notification notification = getNotification();
         if (mChannel.isDemoted()
                 || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
             return false;
         }
-        if (notification.getShortcutId() == null
+        if (mShortcutInfo == null
                 && !FeatureFlagUtils.isEnabled(
                         mContext, FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ)) {
             return false;
@@ -1353,7 +1361,6 @@
         if (mIsNotConversationOverride) {
             return false;
         }
-        // STOPSHIP b/137397357: Check shortcut to make a further decision
         return true;
     }
 
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index e86a42c..f497f11 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -57,6 +57,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.IntentResolver;
 import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.PackageInfoUtils.CachedApplicationInfoGenerator;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.PrintWriter;
@@ -273,9 +274,7 @@
             return null;
         }
         List<ProviderInfo> providerList = null;
-
-        // Map from a package name to the corresponding app info.
-        ArrayMap<String, ApplicationInfo> appInfos = null;
+        CachedApplicationInfoGenerator appInfoGenerator = null;
         synchronized (mLock) {
             for (int i = mProviders.mProviders.size() - 1; i >= 0; --i) {
                 final ParsedProvider p = mProviders.mProviders.valueAt(i);
@@ -304,26 +303,15 @@
                         && (p.getMetaData() == null || !p.getMetaData().containsKey(metaDataKey))) {
                     continue;
                 }
-
-                // Make sure we have AppInfo for this provider.
-                final PackageUserState state = ps.readUserState(userId);
-                ApplicationInfo appInfo =
-                        (appInfos == null) ? null : appInfos.get(pkg.getPackageName());
-                if (appInfo == null) {
-                    appInfo = PackageInfoUtils.generateApplicationInfo(
-                            pkg, flags, state, userId, ps);
-                    if (appInfo == null) {
-                        // In this case, we should avoid calling generateApplicationInfo() for
-                        // the same package in subsequent iterations, but appInfo shouldn't be null
-                        // here, so we don't bother.
-                        continue;
-                    }
-                    if (appInfos == null) {
-                        appInfos = new ArrayMap<>(4);
-                    }
-                    appInfos.put(pkg.getPackageName(), appInfo);
+                if (appInfoGenerator == null) {
+                    appInfoGenerator = new CachedApplicationInfoGenerator();
                 }
-                // At this point, appInfo != null.
+                final PackageUserState state = ps.readUserState(userId);
+                final ApplicationInfo appInfo =
+                        appInfoGenerator.generate(pkg, flags, state, userId, ps);
+                if (appInfo == null) {
+                    continue;
+                }
 
                 final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
                         pkg, p, flags, state, appInfo, userId, ps);
@@ -355,14 +343,20 @@
             if (pkg == null) {
                 return null;
             }
-            return PackageInfoUtils.generateProviderInfo(pkg, p, flags,
-                    ps.readUserState(userId), userId, ps);
+            final PackageUserState state = ps.readUserState(userId);
+            ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
+                    pkg, flags, state, userId, ps);
+            if (appInfo == null) {
+                return null;
+            }
+            return PackageInfoUtils.generateProviderInfo(pkg, p, flags, state, appInfo, userId, ps);
         }
     }
 
     void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo, boolean safeMode,
             int userId) {
         synchronized (mLock) {
+            CachedApplicationInfoGenerator appInfoGenerator = null;
             for (int i = mProvidersByAuthority.size() - 1; i >= 0; --i) {
                 final ParsedProvider p = mProvidersByAuthority.valueAt(i);
                 if (!p.isSyncable()) {
@@ -384,9 +378,18 @@
                 if (safeMode && !pkg.isSystem()) {
                     continue;
                 }
-                final ProviderInfo info =
-                        PackageInfoUtils.generateProviderInfo(pkg, p, 0,
-                                ps.readUserState(userId), userId, ps);
+                if (appInfoGenerator == null) {
+                    appInfoGenerator = new CachedApplicationInfoGenerator();
+                }
+                final PackageUserState state = ps.readUserState(userId);
+                final ApplicationInfo appInfo =
+                        appInfoGenerator.generate(pkg, 0, state, userId, ps);
+                if (appInfo == null) {
+                    continue;
+                }
+
+                final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
+                        pkg, p, 0, state, appInfo, userId, ps);
                 if (info == null) {
                     continue;
                 }
@@ -1731,8 +1734,13 @@
             if (userState.instantApp && ps.isUpdateAvailable()) {
                 return null;
             }
+            final ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
+                    pkg, mFlags, userState, userId, ps);
+            if (appInfo == null) {
+                return null;
+            }
             ProviderInfo pi = PackageInfoUtils.generateProviderInfo(pkg, provider, mFlags,
-                    userState, userId, ps);
+                    userState, appInfo, userId, ps);
             if (pi == null) {
                 return null;
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4eac79c..f714af03 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5459,7 +5459,13 @@
                     return null;
                 }
                 PackageUserState state = ps.readUserState(userId);
-                return PackageInfoUtils.generateProviderInfo(pkg, p, flags, state, userId, ps);
+                final ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
+                        pkg, flags, state, userId, ps);
+                if (appInfo == null) {
+                    return null;
+                }
+                return PackageInfoUtils.generateProviderInfo(
+                        pkg, p, flags, state, appInfo, userId, ps);
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index f5ce080..4ab1f396 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -48,7 +48,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
+import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.PackageSetting;
@@ -61,6 +61,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 
@@ -72,6 +73,7 @@
  * @hide
  **/
 public class PackageInfoUtils {
+    private static final String TAG = PackageParser2.TAG;
 
     /**
      * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
@@ -312,35 +314,22 @@
 
     /**
      * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
-     *
-     * @deprecated use {@link #generateProviderInfo(
-     * AndroidPackage, ParsedProvider, int, PackageUserState, ApplicationInfo, int, PackageSetting)}
-     * instead and pass {@link ApplicationInfo} explicitly to avoid generating duplicate instances
-     * of it.
-     */
-    @Nullable
-    @Deprecated
-    public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId,
-            @Nullable PackageSetting pkgSetting) {
-        return generateProviderInfo(pkg, p, flags, state, null, userId, pkgSetting);
-    }
-
-    /**
-     * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
      */
     @Nullable
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
             @PackageManager.ComponentInfoFlags int flags, PackageUserState state,
-            @Nullable ApplicationInfo applicationInfo, int userId,
+            @NonNull ApplicationInfo applicationInfo, int userId,
             @Nullable PackageSetting pkgSetting) {
         if (p == null) return null;
+        if (applicationInfo == null || !pkg.getPackageName().equals(applicationInfo.packageName)) {
+            Slog.wtf(TAG, "AppInfo's package name is different. Expected=" + pkg.getPackageName()
+                    + " actual=" + (applicationInfo == null ? "(null AppInfo)"
+                    : applicationInfo.packageName));
+            applicationInfo = generateApplicationInfo(pkg, flags, state, userId, pkgSetting);
+        }
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
         }
-        if (applicationInfo == null) {
-            applicationInfo = generateApplicationInfo(pkg, flags, state, userId, pkgSetting);
-        }
         ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfo(pkg, p, flags, state,
                 applicationInfo, userId);
         if (info == null) {
@@ -486,4 +475,29 @@
                 | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY);
         // @formatter:on
     }
+
+    /**
+     * Wraps {@link PackageInfoUtils#generateApplicationInfo} with a cache.
+     */
+    public static class CachedApplicationInfoGenerator {
+        // Map from a package name to the corresponding app info.
+        private ArrayMap<String, ApplicationInfo> mCache = new ArrayMap<>();
+
+        /**
+         * {@link PackageInfoUtils#generateApplicationInfo} with a cache.
+         */
+        @Nullable
+        public ApplicationInfo generate(AndroidPackage pkg,
+                @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId,
+                @Nullable PackageSetting pkgSetting) {
+            ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
+            if (appInfo != null) {
+                return appInfo;
+            }
+            appInfo = PackageInfoUtils.generateApplicationInfo(
+                    pkg, flags, state, userId, pkgSetting);
+            mCache.put(pkg.getPackageName(), appInfo);
+            return appInfo;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index f99791a..d561b9c 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -42,7 +42,7 @@
  */
 public class PackageParser2 {
 
-    private static final String TAG = "PackageParser2";
+    static final String TAG = "PackageParser2";
 
     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
     private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b40c2c1..1b5cc6a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3090,7 +3090,7 @@
                         event.getAction(), fallbackAction.keyCode,
                         event.getRepeatCount(), fallbackAction.metaState,
                         event.getDeviceId(), event.getScanCode(),
-                        flags, event.getSource(), event.getDisplayId(), null /* hmac */, null);
+                        flags, event.getSource(), event.getDisplayId(), null);
 
                 if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
                     fallbackEvent.recycle();
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index 233417d..059861b 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -16,8 +16,13 @@
 package com.android.server.power.batterysaver;
 
 import android.annotation.IntDef;
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatterySaverPolicyConfig;
@@ -183,18 +188,15 @@
     private String mEventLogKeys;
 
     /**
-     * Whether vibration should *really* be disabled -- i.e. {@link Policy#disableVibration}
-     * is true *and* {@link #mAccessibilityEnabled} is false.
-     */
-    @GuardedBy("mLock")
-    private boolean mDisableVibrationEffective;
-
-    /**
      * Whether accessibility is currently enabled or not.
      */
     @GuardedBy("mLock")
     private boolean mAccessibilityEnabled;
 
+    /** Whether the phone is projecting in car mode or not. */
+    @GuardedBy("mLock")
+    private boolean mCarModeEnabled;
+
     /** The current default adaptive policy. */
     @GuardedBy("mLock")
     private Policy mDefaultAdaptivePolicy = DEFAULT_ADAPTIVE_POLICY;
@@ -207,6 +209,13 @@
     @GuardedBy("mLock")
     private Policy mFullPolicy = DEFAULT_FULL_POLICY;
 
+    /**
+     * The current effective policy. This is based on the current policy level's policy, with any
+     * required adjustments.
+     */
+    @GuardedBy("mLock")
+    private Policy mEffectivePolicy = OFF_POLICY;
+
     @IntDef(prefix = {"POLICY_LEVEL_"}, value = {
             POLICY_LEVEL_OFF,
             POLICY_LEVEL_ADAPTIVE,
@@ -230,6 +239,20 @@
     private final ContentResolver mContentResolver;
     private final BatterySavingStats mBatterySavingStats;
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
+                    setCarModeEnabled(true);
+                    break;
+                case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
+                    setCarModeEnabled(false);
+                    break;
+            }
+        }
+    };
+
     @GuardedBy("mLock")
     private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
 
@@ -263,16 +286,25 @@
 
         final AccessibilityManager acm = mContext.getSystemService(AccessibilityManager.class);
 
-        acm.addAccessibilityStateChangeListener((enabled) -> {
-            synchronized (mLock) {
-                mAccessibilityEnabled = enabled;
-            }
-            refreshSettings();
-        });
-        final boolean enabled = acm.isEnabled();
+        acm.addAccessibilityStateChangeListener((enabled) -> setAccessibilityEnabled(enabled));
+        final boolean accessibilityEnabled = acm.isEnabled();
         synchronized (mLock) {
-            mAccessibilityEnabled = enabled;
+            mAccessibilityEnabled = accessibilityEnabled;
         }
+
+        final IntentFilter filter = new IntentFilter(
+                UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
+        filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
+        // The ENTER/EXIT_CAR_MODE_PRIORITIZED intents are sent to UserHandle.ALL, so no need to
+        // register as all users here.
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+        final boolean carModeEnabled =
+                mContext.getSystemService(UiModeManager.class).getCurrentModeType()
+                        == Configuration.UI_MODE_TYPE_CAR;
+        synchronized (mLock) {
+            mCarModeEnabled = carModeEnabled;
+        }
+
         onChange(true, null);
     }
 
@@ -299,13 +331,34 @@
         PowerManager.invalidatePowerSaveModeCaches();
     }
 
+    /**
+     * Notifies listeners of a policy change on the handler thread only if the current policy level
+     * is not {@link POLICY_LEVEL_OFF}.
+     */
+    private void maybeNotifyListenersOfPolicyChange() {
+        final BatterySaverPolicyListener[] listeners;
+        synchronized (mLock) {
+            if (getPolicyLevelLocked() == POLICY_LEVEL_OFF) {
+                // Current policy is OFF, so there's no change to notify listeners of.
+                return;
+            }
+            // Don't call out to listeners with the lock held.
+            listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+        }
+
+        mHandler.post(() -> {
+            for (BatterySaverPolicyListener listener : listeners) {
+                listener.onBatterySaverPolicyChanged(this);
+            }
+        });
+    }
+
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         refreshSettings();
     }
 
     private void refreshSettings() {
-        final BatterySaverPolicyListener[] listeners;
         synchronized (mLock) {
             // Load the non-device-specific setting.
             final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS);
@@ -334,16 +387,9 @@
                 // Nothing of note changed.
                 return;
             }
-
-            listeners = mListeners.toArray(new BatterySaverPolicyListener[0]);
         }
 
-        // Notify the listeners.
-        mHandler.post(() -> {
-            for (BatterySaverPolicyListener listener : listeners) {
-                listener.onBatterySaverPolicyChanged(this);
-            }
-        });
+        maybeNotifyListenersOfPolicyChange();
     }
 
     @GuardedBy("mLock")
@@ -404,31 +450,63 @@
 
     @GuardedBy("mLock")
     private void updatePolicyDependenciesLocked() {
-        final Policy currPolicy = getCurrentPolicyLocked();
-        // Update the effective vibration policy.
-        mDisableVibrationEffective = currPolicy.disableVibration
-                && !mAccessibilityEnabled; // Don't disable vibration when accessibility is on.
+        final Policy rawPolicy = getCurrentRawPolicyLocked();
+
+        final int locationMode;
+        if (mCarModeEnabled
+                && rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE
+                && rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) {
+            // If car projection is enabled, ensure that navigation works.
+            locationMode = PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+        } else {
+            locationMode = rawPolicy.locationMode;
+        }
+        mEffectivePolicy = new Policy(
+                rawPolicy.adjustBrightnessFactor,
+                rawPolicy.advertiseIsEnabled,
+                rawPolicy.deferFullBackup,
+                rawPolicy.deferKeyValueBackup,
+                rawPolicy.disableAnimation,
+                rawPolicy.disableAod,
+                rawPolicy.disableLaunchBoost,
+                rawPolicy.disableOptionalSensors,
+                rawPolicy.disableSoundTrigger,
+                // Don't disable vibration when accessibility is on.
+                rawPolicy.disableVibration && !mAccessibilityEnabled,
+                rawPolicy.enableAdjustBrightness,
+                rawPolicy.enableDataSaver,
+                rawPolicy.enableFirewall,
+                // Don't force night mode when car projection is enabled.
+                rawPolicy.enableNightMode && !mCarModeEnabled,
+                rawPolicy.enableQuickDoze,
+                rawPolicy.filesForInteractive,
+                rawPolicy.filesForNoninteractive,
+                rawPolicy.forceAllAppsStandby,
+                rawPolicy.forceBackgroundCheck,
+                locationMode
+        );
+
 
         final StringBuilder sb = new StringBuilder();
 
-        if (currPolicy.forceAllAppsStandby) sb.append("A");
-        if (currPolicy.forceBackgroundCheck) sb.append("B");
+        if (mEffectivePolicy.forceAllAppsStandby) sb.append("A");
+        if (mEffectivePolicy.forceBackgroundCheck) sb.append("B");
 
-        if (mDisableVibrationEffective) sb.append("v");
-        if (currPolicy.disableAnimation) sb.append("a");
-        if (currPolicy.disableSoundTrigger) sb.append("s");
-        if (currPolicy.deferFullBackup) sb.append("F");
-        if (currPolicy.deferKeyValueBackup) sb.append("K");
-        if (currPolicy.enableFirewall) sb.append("f");
-        if (currPolicy.enableDataSaver) sb.append("d");
-        if (currPolicy.enableAdjustBrightness) sb.append("b");
+        if (mEffectivePolicy.disableVibration) sb.append("v");
+        if (mEffectivePolicy.disableAnimation) sb.append("a");
+        if (mEffectivePolicy.disableSoundTrigger) sb.append("s");
+        if (mEffectivePolicy.deferFullBackup) sb.append("F");
+        if (mEffectivePolicy.deferKeyValueBackup) sb.append("K");
+        if (mEffectivePolicy.enableFirewall) sb.append("f");
+        if (mEffectivePolicy.enableDataSaver) sb.append("d");
+        if (mEffectivePolicy.enableAdjustBrightness) sb.append("b");
 
-        if (currPolicy.disableLaunchBoost) sb.append("l");
-        if (currPolicy.disableOptionalSensors) sb.append("S");
-        if (currPolicy.disableAod) sb.append("o");
-        if (currPolicy.enableQuickDoze) sb.append("q");
+        if (mEffectivePolicy.disableLaunchBoost) sb.append("l");
+        if (mEffectivePolicy.disableOptionalSensors) sb.append("S");
+        if (mEffectivePolicy.disableAod) sb.append("o");
+        if (mEffectivePolicy.enableQuickDoze) sb.append("q");
 
-        sb.append(currPolicy.locationMode);
+        sb.append(mEffectivePolicy.locationMode);
 
         mEventLogKeys = sb.toString();
     }
@@ -857,7 +935,7 @@
                     return builder.setBatterySaverEnabled(currPolicy.disableSoundTrigger)
                             .build();
                 case ServiceType.VIBRATION:
-                    return builder.setBatterySaverEnabled(mDisableVibrationEffective)
+                    return builder.setBatterySaverEnabled(currPolicy.disableVibration)
                             .build();
                 case ServiceType.FORCE_ALL_APPS_STANDBY:
                     return builder.setBatterySaverEnabled(currPolicy.forceAllAppsStandby)
@@ -933,6 +1011,10 @@
     }
 
     private Policy getCurrentPolicyLocked() {
+        return mEffectivePolicy;
+    }
+
+    private Policy getCurrentRawPolicyLocked() {
         switch (getPolicyLevelLocked()) {
             case POLICY_LEVEL_FULL:
                 return mFullPolicy;
@@ -994,11 +1076,13 @@
             pw.println("    value: " + mAdaptiveDeviceSpecificSettings);
 
             pw.println("  mAccessibilityEnabled=" + mAccessibilityEnabled);
+            pw.println("  mCarModeEnabled=" + mCarModeEnabled);
             pw.println("  mPolicyLevel=" + getPolicyLevelLocked());
 
             dumpPolicyLocked(pw, "  ", "full", mFullPolicy);
             dumpPolicyLocked(pw, "  ", "default adaptive", mDefaultAdaptivePolicy);
             dumpPolicyLocked(pw, "  ", "current adaptive", mAdaptivePolicy);
+            dumpPolicyLocked(pw, "  ", "effective", mEffectivePolicy);
         }
     }
 
@@ -1009,11 +1093,7 @@
         pw.print(indent);
         pw.println("  " + KEY_ADVERTISE_IS_ENABLED + "=" + p.advertiseIsEnabled);
         pw.print(indent);
-        pw.println("  " + KEY_VIBRATION_DISABLED + ":config=" + p.disableVibration);
-        // mDisableVibrationEffective is based on the currently selected policy
-        pw.print(indent);
-        pw.println("  " + KEY_VIBRATION_DISABLED + ":effective=" + (p.disableVibration
-                && !mAccessibilityEnabled));
+        pw.println("  " + KEY_VIBRATION_DISABLED + "=" + p.disableVibration);
         pw.print(indent);
         pw.println("  " + KEY_ANIMATION_DISABLED + "=" + p.disableAnimation);
         pw.print(indent);
@@ -1070,10 +1150,24 @@
     }
 
     @VisibleForTesting
-    public void setAccessibilityEnabledForTest(boolean enabled) {
+    void setAccessibilityEnabled(boolean enabled) {
         synchronized (mLock) {
-            mAccessibilityEnabled = enabled;
-            updatePolicyDependenciesLocked();
+            if (mAccessibilityEnabled != enabled) {
+                mAccessibilityEnabled = enabled;
+                updatePolicyDependenciesLocked();
+                maybeNotifyListenersOfPolicyChange();
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void setCarModeEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (mCarModeEnabled != enabled) {
+                mCarModeEnabled = enabled;
+                updatePolicyDependenciesLocked();
+                maybeNotifyListenersOfPolicyChange();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 68a7188..4fea36c 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -275,8 +275,9 @@
         }
 
         /** @return {@code true} if the activity matches a launched activity in this transition. */
-        boolean contains(ActivityRecord r) {
-            return r == mLastLaunchedActivity || mPendingDrawActivities.contains(r);
+        boolean contains(WindowContainer wc) {
+            final ActivityRecord r = AppTransitionController.getAppFromContainer(wc);
+            return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r));
         }
 
         /** Called when the activity is drawn or won't be drawn. */
@@ -435,10 +436,10 @@
 
     /** @return Non-null {@link TransitionInfo} if the activity is found in an active transition. */
     @Nullable
-    private TransitionInfo getActiveTransitionInfo(ActivityRecord r) {
+    private TransitionInfo getActiveTransitionInfo(WindowContainer wc) {
         for (int i = mTransitionInfoList.size() - 1; i >= 0; i--) {
             final TransitionInfo info = mTransitionInfoList.get(i);
-            if (info.contains(r)) {
+            if (info.contains(wc)) {
                 return info;
             }
         }
@@ -623,19 +624,19 @@
      * @param activityToReason A map from activity to a reason integer, which must be on of
      *                         ActivityTaskManagerInternal.APP_TRANSITION_* reasons.
      */
-    void notifyTransitionStarting(ArrayMap<ActivityRecord, Integer> activityToReason) {
+    void notifyTransitionStarting(ArrayMap<WindowContainer, Integer> activityToReason) {
         if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
 
         final long timestampNs = SystemClock.elapsedRealtimeNanos();
         for (int index = activityToReason.size() - 1; index >= 0; index--) {
-            final ActivityRecord r = activityToReason.keyAt(index);
-            final TransitionInfo info = getActiveTransitionInfo(r);
+            final WindowContainer wc = activityToReason.keyAt(index);
+            final TransitionInfo info = getActiveTransitionInfo(wc);
             if (info == null || info.mLoggedTransitionStarting) {
                 // Ignore any subsequent notifyTransitionStarting.
                 continue;
             }
             if (DEBUG_METRICS) {
-                Slog.i(TAG, "notifyTransitionStarting activity=" + r + " info=" + info);
+                Slog.i(TAG, "notifyTransitionStarting activity=" + wc + " info=" + info);
             }
 
             info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d715ed4..e5b8403 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -40,7 +40,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.activityTypeToString;
@@ -106,7 +105,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
-import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_UNSET;
@@ -284,7 +282,6 @@
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.InputApplicationHandle;
-import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -577,12 +574,6 @@
      */
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
-    /**
-     * This leash is used to "freeze" the app surface in place after the state change, but before
-     * the animation is ready to start.
-     */
-    private SurfaceControl mTransitChangeLeash = null;
-
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
     private boolean mLastSurfaceShowing = true;
 
@@ -1329,15 +1320,6 @@
             mDisplayContent.executeAppTransition();
         }
 
-        if (prevDc.mChangingApps.remove(this)) {
-            // This gets called *after* the ActivityRecord has been reparented to the new display.
-            // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN),
-            // so this token is now "frozen" while waiting for the animation to start on prevDc
-            // (which will be cancelled since the window is no-longer a child). However, since this
-            // is no longer a child of prevDc, this won't be notified of the cancelled animation,
-            // so we need to cancel the change transition here.
-            clearChangeLeash(getPendingTransaction(), true /* cancel */);
-        }
         prevDc.mClosingApps.remove(this);
 
         if (prevDc.mFocusedApp == this) {
@@ -3092,7 +3074,7 @@
         commitVisibility(false /* visible */, true /* performLayout */);
 
         getDisplayContent().mOpeningApps.remove(this);
-        getDisplayContent().mChangingApps.remove(this);
+        getDisplayContent().mChangingContainers.remove(this);
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mTaskSnapshotController.onAppRemoved(this);
         mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
@@ -3995,13 +3977,11 @@
                 appToken, visible, appTransition, isVisible(), mVisibleRequested,
                 Debug.getCallers(6));
 
+        onChildVisibilityRequested(visible);
+
         final DisplayContent displayContent = getDisplayContent();
         displayContent.mOpeningApps.remove(this);
         displayContent.mClosingApps.remove(this);
-        if (isInChangeTransition()) {
-            clearChangeLeash(getPendingTransaction(), true /* cancel */);
-        }
-        displayContent.mChangingApps.remove(this);
         waitingToShow = false;
         mVisibleRequested = visible;
         mLastDeferHidingClient = deferHidingClient;
@@ -5805,11 +5785,6 @@
         return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation;
     }
 
-    @Override
-    boolean isChangingAppTransition() {
-        return task != null ? task.isChangingAppTransition() : super.isChangingAppTransition();
-    }
-
     /**
      * Creates a layer to apply crop to an animation.
      */
@@ -5830,84 +5805,19 @@
                 this, endDeferFinishCallback);
     }
 
-    private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
-        if (mWmService.mDisableTransitionAnimation
-                || !isVisible()
-                || getDisplayContent().mAppTransition.isTransitionSet()
-                || getSurfaceControl() == null) {
-            return false;
-        }
-        // Only do an animation into and out-of freeform mode for now. Other mode
-        // transition animations are currently handled by system-ui.
-        return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
-    }
-
     @Override
     boolean isWaitingForTransitionStart() {
         final DisplayContent dc = getDisplayContent();
         return dc != null && dc.mAppTransition.isTransitionSet()
                 && (dc.mOpeningApps.contains(this)
                 || dc.mClosingApps.contains(this)
-                || dc.mChangingApps.contains(this));
+                || dc.mChangingContainers.contains(this));
     }
 
-    /**
-     * Initializes a change transition. Because the app is visible already, there is a small period
-     * of time where the user can see the app content/window update before the transition starts.
-     * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which
-     * "freezes" the location/crop until the transition starts.
-     * <p>
-     * Here's a walk-through of the process:
-     * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it.
-     * 2. Set the temporary leash's position/crop to the current state.
-     * 3. Create a snapshot and place that at the top of the leash to cover up content changes.
-     * 4. Once the transition is ready, it will reparent the app to the animation leash.
-     * 5. Detach the interim-change-leash.
-     */
-    private void initializeChangeTransition(Rect startBounds) {
-        mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
-                false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
-        mDisplayContent.mChangingApps.add(this);
-        mTransitStartRect.set(startBounds);
-
-        final SurfaceControl.Builder builder = makeAnimationLeash()
-                .setParent(getAnimationLeashParent())
-                .setName(getSurfaceControl() + " - interim-change-leash");
-        mTransitChangeLeash = builder.build();
-        Transaction t = getPendingTransaction();
-        t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height());
-        t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top);
-        t.show(mTransitChangeLeash);
-        t.reparent(getSurfaceControl(), mTransitChangeLeash);
-        onAnimationLeashCreated(t, mTransitChangeLeash);
-
-        // Skip creating snapshot if this transition is controlled by a remote animator which
-        // doesn't need it.
-        ArraySet<Integer> activityTypes = new ArraySet<>();
-        activityTypes.add(getActivityType());
-        RemoteAnimationAdapter adapter =
-                mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
-                        this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes);
-        if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
-            return;
-        }
-
-        if (mThumbnail == null && task != null && !hasCommittedReparentToAnimationLeash()) {
-            SurfaceControl.ScreenshotGraphicBuffer snapshot =
-                    mWmService.mTaskSnapshotController.createTaskSnapshot(
-                            task, 1 /* scaleFraction */);
-            if (snapshot != null) {
-                mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory, t, this,
-                        snapshot.getGraphicBuffer(), true /* relative */);
-            }
-        }
-    }
-
-    @Override
-    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+    private int getAnimationLayer() {
         // The leash is parented to the animation layer. We need to preserve the z-order by using
         // the prefix order index, but we boost if necessary.
-        int layer = 0;
+        int layer;
         if (!inPinnedWindowingMode()) {
             layer = getPrefixOrderIndex();
         } else {
@@ -5920,21 +5830,17 @@
         if (mNeedsZBoost) {
             layer += Z_BOOST_BASE;
         }
-        if (!mNeedsAnimationBoundsLayer) {
-            t.setLayer(leash, layer);
-        }
+        return layer;
+    }
 
-        final DisplayContent dc = getDisplayContent();
-        dc.assignStackOrdering();
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+        t.setLayer(leash, getAnimationLayer());
+        getDisplayContent().assignStackOrdering();
+    }
 
-        if (leash == mTransitChangeLeash) {
-            // This is a temporary state so skip any animation notifications
-            return;
-        } else if (mTransitChangeLeash != null) {
-            // unparent mTransitChangeLeash for clean-up
-            clearChangeLeash(t, false /* cancel */);
-        }
-
+    @Override
+    public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
         if (mAnimatingActivityRegistry != null) {
             mAnimatingActivityRegistry.notifyStarting(this);
         }
@@ -5962,7 +5868,8 @@
                 // surface size has already same as the animating container.
                 t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
             }
-            t.setLayer(mAnimationBoundsLayer, layer);
+            t.setLayer(leash, 0);
+            t.setLayer(mAnimationBoundsLayer, getAnimationLayer());
 
             // Reparent leash to animation bounds layer.
             t.reparent(leash, mAnimationBoundsLayer);
@@ -5994,10 +5901,6 @@
         return mLastSurfaceShowing;
     }
 
-    boolean isInChangeTransition() {
-        return mTransitChangeLeash != null || AppTransition.isChangeTransit(mTransit);
-    }
-
     void attachThumbnailAnimation() {
         if (!isAnimating(PARENTS)) {
             return;
@@ -6134,31 +6037,6 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
-    /**
-     * @param cancel {@code true} if clearing the leash due to cancelling instead of transferring
-     *                            to another leash.
-     */
-    private void clearChangeLeash(Transaction t, boolean cancel) {
-        if (mTransitChangeLeash == null) {
-            return;
-        }
-        if (cancel) {
-            clearThumbnail();
-            SurfaceControl sc = getSurfaceControl();
-            SurfaceControl parentSc = getParentSurfaceControl();
-            // Don't reparent if surface is getting destroyed
-            if (parentSc != null && sc != null) {
-                t.reparent(sc, getParentSurfaceControl());
-            }
-        }
-        t.hide(mTransitChangeLeash);
-        t.remove(mTransitChangeLeash);
-        mTransitChangeLeash = null;
-        if (cancel) {
-            onAnimationLeashLost(t);
-        }
-    }
-
     void clearAnimatingFlags() {
         boolean wallpaperMightChange = false;
         for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -6174,7 +6052,7 @@
     void cancelAnimation() {
         cancelAnimationOnly();
         clearThumbnail();
-        clearChangeLeash(getPendingTransaction(), true /* cancel */);
+        mSurfaceFreezer.unfreeze(getPendingTransaction());
     }
 
     /**
@@ -6219,6 +6097,7 @@
         mRemoteAnimationDefinition = null;
     }
 
+    @Override
     RemoteAnimationDefinition getRemoteAnimationDefinition() {
         return mRemoteAnimationDefinition;
     }
@@ -6679,8 +6558,6 @@
                 return;
             }
         }
-        final int prevWinMode = getWindowingMode();
-        mTmpPrevBounds.set(getBounds());
         super.onConfigurationChanged(newParentConfig);
 
         if (shouldUseSizeCompatMode()) {
@@ -6705,12 +6582,6 @@
             }
         }
 
-        final int newWinMode = getWindowingMode();
-        if ((prevWinMode != newWinMode) && (mDisplayContent != null)
-                && shouldStartChangeTransition(prevWinMode, newWinMode)) {
-            initializeChangeTransition(mTmpPrevBounds);
-        }
-
         // Configuration's equality doesn't consider seq so if only seq number changes in resolved
         // override configuration. Therefore ConfigurationContainer doesn't change merged override
         // configuration, but it's used to push configuration changes so explicitly update that.
@@ -7595,6 +7466,7 @@
         }
     }
 
+    @Override
     void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(HASH_CODE, System.identityHashCode(this));
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 693a5e4..d2d7ad3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5868,8 +5868,8 @@
      * Return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} to resolve secondary home
      * activities.
      *
-     * @param preferredPackage Specify a preferred package name, otherwise use secondary home
-     *                        component defined in config_secondaryHomeComponent.
+     * @param preferredPackage Specify a preferred package name, otherwise use the package name
+     *                         defined in config_secondaryHomePackage.
      * @return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME}
      */
     Intent getSecondaryHomeIntent(String preferredPackage) {
@@ -5877,10 +5877,10 @@
         final boolean useSystemProvidedLauncher = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
         if (preferredPackage == null || useSystemProvidedLauncher) {
-            // Using the component stored in config if no package name or forced.
-            final String secondaryHomeComponent = mContext.getResources().getString(
-                    com.android.internal.R.string.config_secondaryHomeComponent);
-            intent.setComponent(ComponentName.unflattenFromString(secondaryHomeComponent));
+            // Using the package name stored in config if no preferred package name or forced.
+            final String secondaryHomePackage = mContext.getResources().getString(
+                    com.android.internal.R.string.config_secondaryHomePackage);
+            intent.setPackage(secondaryHomePackage);
         } else {
             intent.setPackage(preferredPackage);
         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 8cf0881..ca09537 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2308,14 +2308,14 @@
             }
             notifyAppTransitionTimeoutLocked();
             if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
-                    || !dc.mChangingApps.isEmpty()) {
+                    || !dc.mChangingContainers.isEmpty()) {
                 ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                             "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b "
                                     + "mOpeningApps.size()=%d mClosingApps.size()=%d "
                                     + "mChangingApps.size()=%d",
                             dc.getDisplayId(), dc.mAppTransition.isTransitionSet(),
                             dc.mOpeningApps.size(), dc.mClosingApps.size(),
-                            dc.mChangingApps.size());
+                            dc.mChangingContainers.size());
 
                 setTimeout();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 3f4e791..0912b2e 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -55,6 +55,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -86,7 +87,7 @@
     private final WallpaperController mWallpaperControllerLocked;
     private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
 
-    private final ArrayMap<ActivityRecord, Integer> mTempTransitionReasons = new ArrayMap<>();
+    private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
 
     AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -104,7 +105,8 @@
     void handleAppTransitionReady() {
         mTempTransitionReasons.clear();
         if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
-                || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) {
+                || !transitionGoodToGo(mDisplayContent.mChangingContainers,
+                        mTempTransitionReasons)) {
             return;
         }
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
@@ -130,17 +132,21 @@
             // transition selection depends on wallpaper target visibility.
             mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
         }
-        appCount = mDisplayContent.mChangingApps.size();
+        appCount = mDisplayContent.mChangingContainers.size();
         for (int i = 0; i < appCount; ++i) {
             // Clearing for same reason as above.
-            mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags();
+            final ActivityRecord activity = getAppFromContainer(
+                    mDisplayContent.mChangingContainers.valueAtUnchecked(i));
+            if (activity != null) {
+                activity.clearAnimatingFlags();
+            }
         }
 
         // Adjust wallpaper before we pull the lower/upper target, since pending changes
         // (like the clearAnimatingFlags() above) might affect wallpaper target result.
         // Or, the opening app window should be a wallpaper target.
         mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
-                mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps);
+                mDisplayContent.mOpeningApps);
 
         // Determine if closing and opening app token sets are wallpaper targets, in which case
         // special animations are needed.
@@ -159,7 +165,7 @@
         // no need to do an animation. This is the case, for example, when this transition is being
         // done behind a dream window.
         final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
-                mDisplayContent.mClosingApps, mDisplayContent.mChangingApps);
+                mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
         final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw();
         final ActivityRecord animLpActivity = allowAnimations
                 ? findAnimLayoutParamsToken(transit, activityTypes)
@@ -171,14 +177,13 @@
                 ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */)
                 : null;
         final ActivityRecord topChangingApp = allowAnimations
-                ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */)
+                ? getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */)
                 : null;
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
         overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
 
         final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
-                || containsVoiceInteraction(mDisplayContent.mOpeningApps)
-                || containsVoiceInteraction(mDisplayContent.mChangingApps);
+                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
 
         final int layoutRedo;
         mService.mSurfaceAnimationRunner.deferStartingAnimations();
@@ -206,7 +211,7 @@
 
         mDisplayContent.mOpeningApps.clear();
         mDisplayContent.mClosingApps.clear();
-        mDisplayContent.mChangingApps.clear();
+        mDisplayContent.mChangingContainers.clear();
         mDisplayContent.mUnknownAppVisibilityController.clear();
 
         // This has changed the visibility of windows, so perform
@@ -235,9 +240,9 @@
         return mainWindow != null ? mainWindow.mAttrs : null;
     }
 
-    RemoteAnimationAdapter getRemoteAnimationOverride(ActivityRecord animLpActivity,
+    RemoteAnimationAdapter getRemoteAnimationOverride(@NonNull WindowContainer container,
             @TransitionType int transit, ArraySet<Integer> activityTypes) {
-        final RemoteAnimationDefinition definition = animLpActivity.getRemoteAnimationDefinition();
+        final RemoteAnimationDefinition definition = container.getRemoteAnimationDefinition();
         if (definition != null) {
             final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
             if (adapter != null) {
@@ -271,6 +276,11 @@
         }
     }
 
+    static ActivityRecord getAppFromContainer(WindowContainer wc) {
+        return wc.asTask() != null ? wc.asTask().getTopNonFinishingActivity()
+                : wc.asActivityRecord();
+    }
+
     /**
      * @return The window token that determines the animation theme.
      */
@@ -279,14 +289,14 @@
         ActivityRecord result;
         final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
         final ArraySet<ActivityRecord> openingApps = mDisplayContent.mOpeningApps;
-        final ArraySet<ActivityRecord> changingApps = mDisplayContent.mChangingApps;
+        final ArraySet<WindowContainer> changingApps = mDisplayContent.mChangingContainers;
 
         // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
         result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.getRemoteAnimationDefinition() != null
                         && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
         if (result != null) {
-            return result;
+            return getAppFromContainer(result);
         }
         result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.fillsParent() && w.findMainWindow() != null);
@@ -302,7 +312,7 @@
      *         of apps in {@code array1}, {@code array2}, and {@code array3}.
      */
     private static ArraySet<Integer> collectActivityTypes(ArraySet<ActivityRecord> array1,
-            ArraySet<ActivityRecord> array2, ArraySet<ActivityRecord> array3) {
+            ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3) {
         final ArraySet<Integer> result = new ArraySet<>();
         for (int i = array1.size() - 1; i >= 0; i--) {
             result.add(array1.valueAt(i).getActivityType());
@@ -317,7 +327,7 @@
     }
 
     private static ActivityRecord lookForHighestTokenWithFilter(ArraySet<ActivityRecord> array1,
-            ArraySet<ActivityRecord> array2, ArraySet<ActivityRecord> array3,
+            ArraySet<ActivityRecord> array2, ArraySet<WindowContainer> array3,
             Predicate<ActivityRecord> filter) {
         final int array2base = array1.size();
         final int array3base = array2.size() + array2base;
@@ -325,15 +335,16 @@
         int bestPrefixOrderIndex = Integer.MIN_VALUE;
         ActivityRecord bestToken = null;
         for (int i = 0; i < count; i++) {
-            final ActivityRecord wtoken = i < array2base
+            final WindowContainer wtoken = i < array2base
                     ? array1.valueAt(i)
                     : (i < array3base
                             ? array2.valueAt(i - array2base)
                             : array3.valueAt(i - array3base));
             final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
-            if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
+            final ActivityRecord r = getAppFromContainer(wtoken);
+            if (r != null && filter.test(r) && prefixOrderIndex > bestPrefixOrderIndex) {
                 bestPrefixOrderIndex = prefixOrderIndex;
-                bestToken = wtoken;
+                bestToken = r;
             }
         }
         return bestToken;
@@ -589,21 +600,13 @@
     }
 
     private void handleChangingApps(@TransitionType int transit) {
-        final ArraySet<ActivityRecord> apps = mDisplayContent.mChangingApps;
+        final ArraySet<WindowContainer> apps = mDisplayContent.mChangingContainers;
         final int appsCount = apps.size();
         for (int i = 0; i < appsCount; i++) {
-            ActivityRecord activity = apps.valueAt(i);
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", activity);
-            activity.cancelAnimationOnly();
-            activity.applyAnimation(null, transit, true, false,
+            WindowContainer wc = apps.valueAt(i);
+            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc);
+            wc.applyAnimation(null, transit, true, false,
                     null /* animationFinishedCallback */);
-            activity.updateReportedVisibilityLocked();
-            mService.openSurfaceTransaction();
-            try {
-                activity.showAllWindowsLocked();
-            } finally {
-                mService.closeSurfaceTransaction("handleChangingApps");
-            }
         }
     }
 
@@ -628,8 +631,8 @@
         }
     }
 
-    private boolean transitionGoodToGo(ArraySet<ActivityRecord> apps,
-            ArrayMap<ActivityRecord, Integer> outReasons) {
+    private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
+            ArrayMap<WindowContainer, Integer> outReasons) {
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
                 mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
@@ -652,13 +655,17 @@
                 return false;
             }
             for (int i = 0; i < apps.size(); i++) {
-                ActivityRecord activity = apps.valueAt(i);
+                WindowContainer wc = apps.valueAt(i);
+                final ActivityRecord activity = getAppFromContainer(wc);
+                if (activity == null) {
+                    continue;
+                }
                 ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                                "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
-                                        + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
-                                activity, activity.allDrawn, activity.startingDisplayed,
-                                activity.startingMoved, activity.isRelaunching(),
-                                activity.startingWindow);
+                        "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+                                + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
+                        activity, activity.allDrawn, activity.startingDisplayed,
+                        activity.startingMoved, activity.isRelaunching(),
+                        activity.startingWindow);
 
 
                 final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
@@ -838,7 +845,7 @@
     @VisibleForTesting
     boolean isTransitWithinTask(@TransitionType int transit, Task task) {
         if (task == null
-                || !mDisplayContent.mChangingApps.isEmpty()) {
+                || !mDisplayContent.mChangingContainers.isEmpty()) {
             // if there is no task, then we can't constrain to the task.
             // if anything is changing, it can animate outside its task.
             return false;
@@ -882,12 +889,13 @@
      *                        {@link ActivityRecord#isVisible}.
      * @return The top {@link ActivityRecord}.
      */
-    private ActivityRecord getTopApp(ArraySet<ActivityRecord> apps, boolean ignoreInvisible) {
+    private ActivityRecord getTopApp(ArraySet<? extends WindowContainer> apps,
+            boolean ignoreInvisible) {
         int topPrefixOrderIndex = Integer.MIN_VALUE;
         ActivityRecord topApp = null;
         for (int i = apps.size() - 1; i >= 0; i--) {
-            final ActivityRecord app = apps.valueAt(i);
-            if (ignoreInvisible && !app.isVisible()) {
+            final ActivityRecord app = getAppFromContainer(apps.valueAt(i));
+            if (app == null || ignoreInvisible && !app.isVisible()) {
                 continue;
             }
             final int prefixOrderIndex = app.getPrefixOrderIndex();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8ccb59f..30e8da2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -91,7 +91,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.CHANGING_APPS;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
@@ -314,7 +313,7 @@
 
     final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
     final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
-    final ArraySet<ActivityRecord> mChangingApps = new ArraySet<>();
+    final ArraySet<WindowContainer> mChangingContainers = new ArraySet<>();
     final UnknownAppVisibilityController mUnknownAppVisibilityController;
 
     private MetricsLogger mMetricsLogger;
@@ -2695,7 +2694,7 @@
             // Clear all transitions & screen frozen states when removing display.
             mOpeningApps.clear();
             mClosingApps.clear();
-            mChangingApps.clear();
+            mChangingContainers.clear();
             mUnknownAppVisibilityController.clear();
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             handleAnimatingStoppedAndTransition();
@@ -2712,6 +2711,9 @@
             mRemovingDisplay = false;
         }
 
+        // Apply the pending transaction here since we may not be able to reach the DisplayContent
+        // on the next traversal if it's removed from RootWindowContainer child list.
+        getPendingTransaction().apply();
         mWmService.mWindowPlacerLocked.requestTraversal();
     }
 
@@ -2913,9 +2915,6 @@
         for (int i = mClosingApps.size() - 1; i >= 0; i--) {
             mClosingApps.valueAt(i).writeIdentifierToProto(proto, CLOSING_APPS);
         }
-        for (int i = mChangingApps.size() - 1; i >= 0; i--) {
-            mChangingApps.valueAt(i).writeIdentifierToProto(proto, CHANGING_APPS);
-        }
 
         proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
         final ActivityStack focusedStack = getFocusedStack();
@@ -3667,7 +3666,7 @@
             }
         }
 
-        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) {
+        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingContainers.isEmpty()) {
             pw.println();
             if (mOpeningApps.size() > 0) {
                 pw.print("  mOpeningApps="); pw.println(mOpeningApps);
@@ -3675,8 +3674,8 @@
             if (mClosingApps.size() > 0) {
                 pw.print("  mClosingApps="); pw.println(mClosingApps);
             }
-            if (mChangingApps.size() > 0) {
-                pw.print("  mChangingApps="); pw.println(mChangingApps);
+            if (mChangingContainers.size() > 0) {
+                pw.print("  mChangingApps="); pw.println(mChangingContainers);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e69551a..9468bff 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -500,7 +500,7 @@
         }
 
         if (mTargetActivityRecord != null) {
-            final ArrayMap<ActivityRecord, Integer> reasons = new ArrayMap<>(1);
+            final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(1);
             reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM);
             mService.mAtmService.mStackSupervisor.getActivityMetricsLogger()
                     .notifyTransitionStarting(reasons);
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index d2dbab8..0eb9daf 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -387,7 +387,7 @@
             final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
             if (dc.mOpeningApps.contains(topActivity)) {
                 return RemoteAnimationTarget.MODE_OPENING;
-            } else if (dc.mChangingApps.contains(topActivity)) {
+            } else if (dc.mChangingContainers.contains(topActivity)) {
                 return RemoteAnimationTarget.MODE_CHANGING;
             } else {
                 return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 7164cd8..1e54e69 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -128,7 +128,8 @@
      */
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
             @AnimationType int type,
-            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
+            @Nullable SurfaceFreezer freezer) {
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mAnimation = anim;
         mAnimationType = type;
@@ -139,9 +140,14 @@
             cancelAnimation();
             return;
         }
-        mLeash = createAnimationLeash(surface, t,
-                mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), hidden);
-        mAnimatable.onAnimationLeashCreated(t, mLeash);
+        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
+        if (mLeash == null) {
+            mLeash = createAnimationLeash(mAnimatable, surface, t,
+                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
+                    0 /* y */, hidden);
+            mAnimatable.onAnimationLeashCreated(t, mLeash);
+        }
+        mAnimatable.onLeashAnimationStarting(t, mLeash);
         if (mAnimationStartDelayed) {
             if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
             return;
@@ -150,6 +156,12 @@
     }
 
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+            @AnimationType int type,
+            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+        startAnimation(t, anim, hidden, type, animationFinishedCallback, null /* freezer */);
+    }
+
+    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
             @AnimationType int type) {
         startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
     }
@@ -311,15 +323,31 @@
     }
 
     private void reset(Transaction t, boolean destroyLeash) {
-        final SurfaceControl surface = mAnimatable.getSurfaceControl();
-        final SurfaceControl parent = mAnimatable.getParentSurfaceControl();
+        mService.mAnimationTransferMap.remove(mAnimation);
+        mAnimation = null;
+        mAnimationFinishedCallback = null;
+        mAnimationType = ANIMATION_TYPE_NONE;
+        if (mLeash == null) {
+            return;
+        }
+        SurfaceControl leash = mLeash;
+        mLeash = null;
+        final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash);
+        if (scheduleAnim) {
+            mService.scheduleAnimationLocked();
+        }
+    }
 
+    static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
+            boolean destroy) {
         boolean scheduleAnim = false;
+        final SurfaceControl surface = animatable.getSurfaceControl();
+        final SurfaceControl parent = animatable.getParentSurfaceControl();
 
         // If the surface was destroyed or the leash is invalid, we don't care to reparent it back.
         // Note that we also set this variable to true even if the parent isn't valid anymore, in
         // order to ensure onAnimationLeashLost still gets called in this case.
-        final boolean reparent = mLeash != null && surface != null;
+        final boolean reparent = surface != null;
         if (reparent) {
             if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
             // We shouldn't really need these isValid checks but we do
@@ -329,40 +357,30 @@
                 scheduleAnim = true;
             }
         }
-        mService.mAnimationTransferMap.remove(mAnimation);
-        if (mLeash != null && destroyLeash) {
-            t.remove(mLeash);
+        if (destroy) {
+            t.remove(leash);
             scheduleAnim = true;
         }
-        mLeash = null;
-        mAnimation = null;
-        mAnimationFinishedCallback = null;
-        mAnimationType = ANIMATION_TYPE_NONE;
 
         if (reparent) {
             // Make sure to inform the animatable after the surface was reparented (or reparent
             // wasn't possible, but we still need to invoke the callback)
-            mAnimatable.onAnimationLeashLost(t);
+            animatable.onAnimationLeashLost(t);
             scheduleAnim = true;
         }
-
-        if (scheduleAnim) {
-            mService.scheduleAnimationLocked();
-        }
+        return scheduleAnim;
     }
 
-    private SurfaceControl createAnimationLeash(SurfaceControl surface, Transaction t, int width,
-            int height, boolean hidden) {
+    static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
+            Transaction t, int width, int height, int x, int y, boolean hidden) {
         if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
-        final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
-                .setParent(mAnimatable.getAnimationLeashParent())
+        final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
+                .setParent(animatable.getAnimationLeashParent())
                 .setHidden(hidden)
                 .setName(surface + " - animation-leash");
         final SurfaceControl leash = builder.build();
         t.setWindowCrop(leash, width, height);
-
-        // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is
-        //       fixed.
+        t.setPosition(leash, x, y);
         t.show(leash);
         t.setAlpha(leash, hidden ? 0 : 1);
 
@@ -489,7 +507,8 @@
         void commitPendingTransaction();
 
         /**
-         * Called when the was created.
+         * Called when the animation leash is created. Note that this is also called by
+         * {@link SurfaceFreezer}, so this doesn't mean we're about to start animating.
          *
          * @param t The transaction to use to apply any necessary changes.
          * @param leash The leash that was created.
@@ -497,6 +516,14 @@
         void onAnimationLeashCreated(Transaction t, SurfaceControl leash);
 
         /**
+         * Called when the animator is about to start animating the leash.
+         *
+         * @param t The transaction to use to apply any necessary changes.
+         * @param leash The leash that was created.
+         */
+        default void onLeashAnimationStarting(Transaction t, SurfaceControl leash) { }
+
+        /**
          * Called when the leash is being destroyed, or when the leash is being transferred to
          * another SurfaceAnimator.
          *
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
new file mode 100644
index 0000000..20435ea
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -0,0 +1,245 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.server.protolog.common.ProtoLog;
+
+import java.util.function.Supplier;
+
+/**
+ * This class handles "freezing" of an Animatable. The Animatable in question should implement
+ * Freezable.
+ *
+ * The point of this is to enable WindowContainers to each be capable of freezing themselves.
+ * Freezing means taking a snapshot and placing it above everything in the sub-hierarchy.
+ * The "placing above" requires that a parent surface be inserted above the target surface so that
+ * the target surface and the snapshot are siblings.
+ *
+ * The overall flow for a transition using this would be:
+ * 1. Set transition and record animatable in mChangingApps
+ * 2. Call {@link #freeze} to set-up the leashes and cover with a snapshot.
+ * 3. When transition participants are ready, start SurfaceAnimator with this as a parameter
+ * 4. SurfaceAnimator will then {@link #takeLeashForAnimation} instead of creating another leash.
+ * 5. The animation system should eventually clean this up via {@link #unfreeze}.
+ */
+class SurfaceFreezer {
+
+    private final Freezable mAnimatable;
+    private final WindowManagerService mWmService;
+    private SurfaceControl mLeash;
+    Snapshot mSnapshot = null;
+    final Rect mFreezeBounds = new Rect();
+
+    /**
+     * @param animatable The object to animate.
+     */
+    SurfaceFreezer(Freezable animatable, WindowManagerService service) {
+        mAnimatable = animatable;
+        mWmService = service;
+    }
+
+    /**
+     * Freeze the target surface. This is done by creating a leash (inserting a parent surface
+     * above the target surface) and then taking a snapshot and placing it over the target surface.
+     *
+     * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
+     */
+    void freeze(SurfaceControl.Transaction t, Rect startBounds) {
+        mFreezeBounds.set(startBounds);
+
+        mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
+                t, startBounds.width(), startBounds.height(), startBounds.left, startBounds.top,
+                false /* hidden */);
+        mAnimatable.onAnimationLeashCreated(t, mLeash);
+
+        SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget();
+        if (freezeTarget != null) {
+            GraphicBuffer snapshot = createSnapshotBuffer(freezeTarget, startBounds);
+            if (snapshot != null) {
+                mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, snapshot, mLeash);
+            }
+        }
+    }
+
+    /**
+     * Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation.
+     * By transferring the leash, this will no longer try to clean-up the leash when finished.
+     */
+    SurfaceControl takeLeashForAnimation() {
+        SurfaceControl out = mLeash;
+        mLeash = null;
+        return out;
+    }
+
+    /**
+     * Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the
+     * snapshot.
+     */
+    void unfreeze(SurfaceControl.Transaction t) {
+        if (mSnapshot != null) {
+            mSnapshot.destroy(t);
+        }
+        if (mLeash == null) {
+            return;
+        }
+        SurfaceControl leash = mLeash;
+        mLeash = null;
+        final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash,
+                false /* destroy */);
+        if (scheduleAnim) {
+            mWmService.scheduleAnimationLocked();
+        }
+    }
+
+    boolean hasLeash() {
+        return mLeash != null;
+    }
+
+    private static GraphicBuffer createSnapshotBuffer(@NonNull SurfaceControl target,
+            @Nullable Rect bounds) {
+        Rect cropBounds = null;
+        if (bounds != null) {
+            cropBounds = new Rect(bounds);
+            cropBounds.offsetTo(0, 0);
+        }
+        final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
+                SurfaceControl.captureLayers(
+                        target, cropBounds, 1.f /* frameScale */, PixelFormat.RGBA_8888);
+        final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer()
+                : null;
+        if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+            return null;
+        }
+        return buffer;
+    }
+
+    class Snapshot {
+        private SurfaceControl mSurfaceControl;
+        private AnimationAdapter mAnimation;
+        private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback;
+
+        /**
+         * @param t Transaction to create the thumbnail in.
+         * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
+         */
+        Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
+                GraphicBuffer thumbnailHeader, SurfaceControl parent) {
+            Surface drawSurface = surfaceFactory.get();
+            // We can't use a delegating constructor since we need to
+            // reference this::onAnimationFinished
+            final int width = thumbnailHeader.getWidth();
+            final int height = thumbnailHeader.getHeight();
+
+            mSurfaceControl = mAnimatable.makeAnimationLeash()
+                    .setName("snapshot anim: " + mAnimatable.toString())
+                    .setBufferSize(width, height)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .setParent(parent)
+                    .build();
+
+            ProtoLog.i(WM_SHOW_TRANSACTIONS, "  THUMBNAIL %s: CREATE", mSurfaceControl);
+
+            // Transfer the thumbnail to the surface
+            drawSurface.copyFrom(mSurfaceControl);
+            drawSurface.attachAndQueueBuffer(thumbnailHeader);
+            drawSurface.release();
+            t.show(mSurfaceControl);
+
+            // We parent the thumbnail to the container, and just place it on top of anything else
+            // in the container.
+            t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+        }
+
+        void destroy(SurfaceControl.Transaction t) {
+            if (mSurfaceControl == null) {
+                return;
+            }
+            t.remove(mSurfaceControl);
+            mSurfaceControl = null;
+        }
+
+        /**
+         * Starts an animation.
+         *
+         * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
+         *             component responsible for running the animation. It runs the animation with
+         *             {@link AnimationAdapter#startAnimation} once the hierarchy with
+         *             the Leash has been set up.
+         * @param animationFinishedCallback The callback being triggered when the animation
+         *                                  finishes.
+         */
+        void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type,
+                @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) {
+            cancelAnimation(t, true /* restarting */);
+            mAnimation = anim;
+            mFinishedCallback = animationFinishedCallback;
+            if (mSurfaceControl == null) {
+                cancelAnimation(t, false /* restarting */);
+                return;
+            }
+            mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback);
+        }
+
+        /**
+         * Cancels the animation, and resets the leash.
+         *
+         * @param t The transaction to use for all cancelling surface operations.
+         * @param restarting Whether we are restarting the animation.
+         */
+        private void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
+            final SurfaceControl leash = mSurfaceControl;
+            final AnimationAdapter animation = mAnimation;
+            final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
+                    mFinishedCallback;
+            mAnimation = null;
+            mFinishedCallback = null;
+            if (animation != null) {
+                animation.onAnimationCancelled(leash);
+                if (!restarting) {
+                    if (animationFinishedCallback != null) {
+                        animationFinishedCallback.onAnimationFinished(
+                                ANIMATION_TYPE_APP_TRANSITION, animation);
+                    }
+                }
+            }
+            if (!restarting) {
+                // TODO: do we need to destroy?
+                destroy(t);
+            }
+        }
+    }
+
+    /** freezable */
+    public interface Freezable extends SurfaceAnimator.Animatable {
+        /**
+         * @return The surface to take a snapshot of. If this returns {@code null}, no snapshot
+         *         will be generated (but the rest of the freezing logic will still happen).
+         */
+        @Nullable SurfaceControl getFreezeSnapshotTarget();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 76805e9..07e17e8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -58,6 +58,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
 import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
@@ -78,6 +79,9 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.IdentifierProto.TITLE;
+import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -119,10 +123,13 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.ITaskOrganizer;
+import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -1884,6 +1891,8 @@
                     .setBounds(mLastNonFullscreenBounds);
         }
 
+        final int prevWinMode = getWindowingMode();
+        mTmpPrevBounds.set(getBounds());
         final boolean wasInMultiWindowMode = inMultiWindowMode();
         super.onConfigurationChanged(newParentConfig);
         if (wasInMultiWindowMode != inMultiWindowMode()) {
@@ -1891,6 +1900,12 @@
             updateShadowsRadius(isFocused(), getPendingTransaction());
         }
 
+        final int newWinMode = getWindowingMode();
+        if ((prevWinMode != newWinMode) && (mDisplayContent != null)
+                && shouldStartChangeTransition(prevWinMode, newWinMode)) {
+            initializeChangeTransition(mTmpPrevBounds);
+        }
+
         // If the configuration supports persistent bounds (eg. Freeform), keep track of the
         // current (non-fullscreen) bounds for persistence.
         if (getWindowConfiguration().persistTaskBounds()) {
@@ -1905,6 +1920,63 @@
     }
 
     /**
+     * Initializes a change transition. See {@link SurfaceFreezer} for more information.
+     */
+    private void initializeChangeTransition(Rect startBounds) {
+        mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
+                false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+        mDisplayContent.mChangingContainers.add(this);
+
+        mSurfaceFreezer.freeze(getPendingTransaction(), startBounds);
+    }
+
+    private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+        if (mWmService.mDisableTransitionAnimation
+                || !isVisible()
+                || getDisplayContent().mAppTransition.isTransitionSet()
+                || getSurfaceControl() == null) {
+            return false;
+        }
+        // Only do an animation into and out-of freeform mode for now. Other mode
+        // transition animations are currently handled by system-ui.
+        return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
+    }
+
+    @VisibleForTesting
+    boolean isInChangeTransition() {
+        return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransit(mTransit);
+    }
+
+    @Override
+    public SurfaceControl getFreezeSnapshotTarget() {
+        final int transit = mDisplayContent.mAppTransition.getAppTransition();
+        if (!AppTransition.isChangeTransit(transit)) {
+            return null;
+        }
+        // Skip creating snapshot if this transition is controlled by a remote animator which
+        // doesn't need it.
+        final ArraySet<Integer> activityTypes = new ArraySet<>();
+        activityTypes.add(getActivityType());
+        final RemoteAnimationAdapter adapter =
+                mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
+                        this, transit, activityTypes);
+        if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
+            return null;
+        }
+        return getSurfaceControl();
+    }
+
+    @Override
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(USER_ID, mUserId);
+        proto.write(TITLE, intent != null && intent.getComponent() != null
+                ? intent.getComponent().flattenToShortString() : "Task");
+        proto.end(token);
+    }
+
+    /**
      * Saves launching state if necessary so that we can launch the activity to its latest state.
      * It only saves state if this task has been shown to user and it's in fullscreen or freeform
      * mode on freeform displays.
@@ -2614,12 +2686,22 @@
         if (!isRootTask) {
             adjustBoundsForDisplayChangeIfNeeded(dc);
         }
+        final DisplayContent prevDc = mDisplayContent;
         super.onDisplayChanged(dc);
         if (!isRootTask) {
             final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
             mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
                     mTaskId, displayId);
         }
+        if (prevDc != null && prevDc.mChangingContainers.remove(this)) {
+            // This gets called *after* this has been reparented to the new display.
+            // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN),
+            // so this token is now "frozen" while waiting for the animation to start on prevDc
+            // (which will be cancelled since the window is no-longer a child). However, since this
+            // is no longer a child of prevDc, this won't be notified of the cancelled animation,
+            // so we need to cancel the change transition here.
+            mSurfaceFreezer.unfreeze(getPendingTransaction());
+        }
     }
 
     /**
@@ -3010,15 +3092,6 @@
         return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); });
     }
 
-    /**
-     * @return {@code true} if changing app transition is running.
-     */
-    @Override
-    boolean isChangingAppTransition() {
-        final ActivityRecord activity = getTopVisibleActivity();
-        return activity != null && getDisplayContent().mChangingApps.contains(activity);
-    }
-
     @Override
     RemoteAnimationTarget createRemoteAnimationTarget(
             RemoteAnimationController.RemoteAnimationRecord record) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5c73f92..f83b052 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,19 +18,18 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
-import static com.android.server.wm.TaskSnapshotPersister.DISABLE_HIGH_RES_BITMAPS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
@@ -89,14 +88,6 @@
     @VisibleForTesting
     static final int SNAPSHOT_MODE_NONE = 2;
 
-    /**
-     * Constant for <code>scaleFactor</code> when calling {@link #snapshotTask} which is
-     * interpreted as using the most appropriate scale ratio for the system.
-     * This may yield a smaller ratio on low memory devices.
-     */
-    @VisibleForTesting
-    static final float SNAPSHOT_SCALE_AUTO = -1f;
-
     private final WindowManagerService mService;
 
     private final TaskSnapshotCache mCache;
@@ -229,7 +220,7 @@
     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
             boolean isLowResolution) {
         return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
-                || DISABLE_HIGH_RES_BITMAPS);
+                && mPersister.enableLowResSnapshots());
     }
 
     /**
@@ -273,8 +264,6 @@
      * information from the task and populates the builder.
      *
      * @param task the task to capture
-     * @param scaleFraction the scale fraction between 0-1.0, or {@link #SNAPSHOT_SCALE_AUTO}
-     *                      to automatically select
      * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
      *                    automatically select
      * @param builder the snapshot builder to populate
@@ -282,8 +271,7 @@
      * @return true if the state of the task is ok to proceed
      */
     @VisibleForTesting
-    boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
-            TaskSnapshot.Builder builder) {
+    boolean prepareTaskSnapshot(Task task, int pixelFormat, TaskSnapshot.Builder builder) {
         if (!mService.mPolicy.isScreenOn()) {
             if (DEBUG_SCREENSHOT) {
                 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -314,18 +302,6 @@
         builder.setId(System.currentTimeMillis());
         builder.setContentInsets(getInsets(mainWindow));
 
-        final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
-
-        if (scaleFraction == SNAPSHOT_SCALE_AUTO) {
-            builder.setScaleFraction(isLowRamDevice
-                    ? mPersister.getLowResScale()
-                    : mHighResTaskSnapshotScale);
-            builder.setIsLowResolution(isLowRamDevice);
-        } else {
-            builder.setScaleFraction(scaleFraction);
-            builder.setIsLowResolution(scaleFraction < 1.0f);
-        }
-
         final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
         final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
 
@@ -351,13 +327,23 @@
 
     @Nullable
     SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
-            float scaleFraction) {
-        return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888);
+            TaskSnapshot.Builder builder) {
+        Point taskSize = new Point();
+        final SurfaceControl.ScreenshotGraphicBuffer taskSnapshot = createTaskSnapshot(task,
+                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize);
+        builder.setTaskSize(taskSize);
+        return taskSnapshot;
     }
 
     @Nullable
     SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
-            float scaleFraction, int pixelFormat) {
+            float scaleFraction) {
+        return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888, null);
+    }
+
+    @Nullable
+    SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
+            float scaleFraction, int pixelFormat, Point outTaskSize) {
         if (task.getSurfaceControl() == null) {
             if (DEBUG_SCREENSHOT) {
                 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
@@ -369,6 +355,10 @@
         final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
                 SurfaceControl.captureLayers(
                         task.getSurfaceControl(), mTmpRect, scaleFraction, pixelFormat);
+        if (outTaskSize != null) {
+            outTaskSize.x = mTmpRect.width();
+            outTaskSize.y = mTmpRect.height();
+        }
         final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer()
                 : null;
         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
@@ -379,21 +369,20 @@
 
     @Nullable
     TaskSnapshot snapshotTask(Task task) {
-        return snapshotTask(task, SNAPSHOT_SCALE_AUTO, PixelFormat.UNKNOWN);
+        return snapshotTask(task, PixelFormat.UNKNOWN);
     }
 
     @Nullable
-    TaskSnapshot snapshotTask(Task task, float scaleFraction, int pixelFormat) {
+    TaskSnapshot snapshotTask(Task task, int pixelFormat) {
         TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
 
-        if (!prepareTaskSnapshot(task, scaleFraction, pixelFormat, builder)) {
+        if (!prepareTaskSnapshot(task, pixelFormat, builder)) {
             // Failed some pre-req. Has been logged.
             return null;
         }
 
         final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
-                createTaskSnapshot(task, builder.getScaleFraction(),
-                builder.getPixelFormat());
+                createTaskSnapshot(task, builder);
 
         if (screenshotBuffer == null) {
             // Failed to acquire image. Has been logged.
@@ -472,8 +461,10 @@
         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
                 mHighResTaskSnapshotScale, mainWindow.getRequestedInsetsState());
-        final int width = (int) (task.getBounds().width() * mHighResTaskSnapshotScale);
-        final int height = (int) (task.getBounds().height() * mHighResTaskSnapshotScale);
+        final int taskWidth = task.getBounds().width();
+        final int taskHeight = task.getBounds().height();
+        final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
+        final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
 
         final RenderNode node = RenderNode.create("TaskSnapshotController", null);
         node.setLeftTopRightBottom(0, 0, width, height);
@@ -494,9 +485,9 @@
                 System.currentTimeMillis() /* id */,
                 topChild.mActivityComponent, hwBitmap.createGraphicBufferHandle(),
                 hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
-                mainWindow.getWindowConfiguration().getRotation(),
-                getInsets(mainWindow), ActivityManager.isLowRamDeviceStatic() /* isLowResolution */,
-                mHighResTaskSnapshotScale, false /* isRealSnapshot */, task.getWindowingMode(),
+                mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
+                getInsets(mainWindow), false /* isLowResolution */,
+                false /* isRealSnapshot */, task.getWindowingMode(),
                 getSystemUiVisibility(task), false);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 01f3427..c20ce5f 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -19,6 +19,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.ComponentName;
 import android.graphics.Bitmap;
@@ -26,6 +27,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.graphics.GraphicBuffer;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Slog;
 
@@ -52,28 +54,110 @@
         mPersister = persister;
     }
 
+    static class PreRLegacySnapshotConfig {
+        /**
+         * If isPreRLegacy is {@code true}, specifies the scale the snapshot was taken at
+         */
+        final float mScale;
+
+        /**
+         * If {@code true}, always load *_reduced.jpg file, no matter what was requested
+         */
+        final boolean mForceLoadReducedJpeg;
+
+        PreRLegacySnapshotConfig(float scale, boolean forceLoadReducedJpeg) {
+            mScale = scale;
+            mForceLoadReducedJpeg = forceLoadReducedJpeg;
+        }
+    }
+
+    /**
+     * When device is upgraded, we might be loading a legacy snapshot. In those cases,
+     * restore the scale based on how it was configured historically. See history of
+     * TaskSnapshotPersister for more information.
+     *
+     *   | low_ram=false                      | low_ram=true
+     *   +------------------------------------------------------------------------------+
+     * O | *.jpg = 100%, *_reduced.jpg = 50%                                            |
+     *   |                                    +-----------------------------------------|
+     * P |                                    | *.jpg = NONE, *_reduced.jpg = 60%       |
+     *   +------------------------------------+-----------------------------------------+
+     * Q | *.jpg = proto.scale,               | *.jpg = NONE,                           |
+     *   | *_reduced.jpg = 50% * proto.scale  | *_reduced.jpg = proto.scale             |
+     *   +------------------------------------+-----------------------------------------+
+     *
+     * @return null if Android R, otherwise a PreRLegacySnapshotConfig object
+     */
+    PreRLegacySnapshotConfig getLegacySnapshotConfig(int taskWidth, float legacyScale,
+            boolean highResFileExists, boolean loadLowResolutionBitmap) {
+        float preRLegacyScale = 0;
+        boolean forceLoadReducedJpeg = false;
+        boolean isPreRLegacySnapshot = (taskWidth == 0);
+        if (!isPreRLegacySnapshot) {
+            return null;
+        }
+        final boolean isPreQLegacyProto = isPreRLegacySnapshot
+                && (Float.compare(legacyScale, 0f) == 0);
+
+        if (isPreQLegacyProto) {
+            // Android O or Android P
+            if (ActivityManager.isLowRamDeviceStatic() && !highResFileExists) {
+                // Android P w/ low_ram=true
+                preRLegacyScale = 0.6f;
+                // Force bitmapFile to always be *_reduced.jpg
+                forceLoadReducedJpeg = true;
+            } else {
+                // Android O, OR Android P w/ low_ram=false
+                preRLegacyScale = loadLowResolutionBitmap ? 0.5f : 1.0f;
+            }
+        } else if (isPreRLegacySnapshot) {
+            // If not pre-Q but is pre-R, then it must be Android Q
+            if (ActivityManager.isLowRamDeviceStatic()) {
+                preRLegacyScale = legacyScale;
+                // Force bitmapFile to always be *_reduced.jpg
+                forceLoadReducedJpeg = true;
+            } else {
+                preRLegacyScale =
+                        loadLowResolutionBitmap ? 0.5f * legacyScale : legacyScale;
+            }
+        }
+        return new PreRLegacySnapshotConfig(preRLegacyScale, forceLoadReducedJpeg);
+    }
+
     /**
      * Loads a task from the disk.
      * <p>
      * Do not hold the window manager lock when calling this method, as we directly read data from
      * disk here, which might be slow.
      *
-     * @param taskId The id of the task to load.
-     * @param userId The id of the user the task belonged to.
-     * @param isLowResolution Whether to load a reduced resolution version of the snapshot.
+     * @param taskId                  The id of the task to load.
+     * @param userId                  The id of the user the task belonged to.
+     * @param loadLowResolutionBitmap Whether to load a low resolution resolution version of the
+     *                                snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId, boolean isLowResolution) {
+    TaskSnapshot loadTask(int taskId, int userId, boolean loadLowResolutionBitmap) {
         final File protoFile = mPersister.getProtoFile(taskId, userId);
-        final File bitmapFile = isLowResolution
-                ? mPersister.getLowResolutionBitmapFile(taskId, userId)
-                : mPersister.getHighResolutionBitmapFile(taskId, userId);
-        if (bitmapFile == null || !protoFile.exists() || !bitmapFile.exists()) {
+        if (!protoFile.exists()) {
             return null;
         }
         try {
             final byte[] bytes = Files.readAllBytes(protoFile.toPath());
             final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
+            final File highResBitmap = mPersister.getHighResolutionBitmapFile(taskId, userId);
+
+            PreRLegacySnapshotConfig legacyConfig = getLegacySnapshotConfig(proto.taskWidth,
+                    proto.legacyScale, highResBitmap.exists(), loadLowResolutionBitmap);
+
+            boolean forceLoadReducedJpeg =
+                    legacyConfig != null && legacyConfig.mForceLoadReducedJpeg;
+            File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
+                    ? mPersister.getLowResolutionBitmapFile(taskId, userId) : highResBitmap;
+
+            if (!bitmapFile.exists()) {
+                return null;
+            }
+
             final Options options = new Options();
             options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent
                     ? Config.RGB_565
@@ -99,13 +183,20 @@
 
             final ComponentName topActivityComponent = ComponentName.unflattenFromString(
                     proto.topActivityComponent);
-            // For legacy snapshots, restore the scale based on the reduced resolution state
-            final float legacyScale = isLowResolution ? mPersister.getLowResScale() : 1f;
-            final float scale = Float.compare(proto.scale, 0f) != 0 ? proto.scale : legacyScale;
-            return new TaskSnapshot(proto.id, topActivityComponent, buffer, hwBitmap.getColorSpace(),
-                    proto.orientation, proto.rotation,
+
+            Point taskSize;
+            if (legacyConfig != null) {
+                int taskWidth = (int) ((float) hwBitmap.getWidth() / legacyConfig.mScale);
+                int taskHeight = (int) ((float) hwBitmap.getHeight() / legacyConfig.mScale);
+                taskSize = new Point(taskWidth, taskHeight);
+            } else {
+                taskSize = new Point(proto.taskWidth, proto.taskHeight);
+            }
+
+            return new TaskSnapshot(proto.id, topActivityComponent, buffer,
+                    hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize,
                     new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
-                    isLowResolution, scale, proto.isRealSnapshot, proto.windowingMode,
+                    loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
                     proto.systemUiVisibility, proto.isTranslucent);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 31212b8..164d3e0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -21,8 +21,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.annotation.TestApi;
-import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -52,8 +52,6 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String LOW_RES_FILE_POSTFIX = "_reduced";
-    private static final float LOW_RAM_REDUCED_SCALE = .8f;
-    static final boolean DISABLE_HIGH_RES_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
     private static final String PROTO_EXTENSION = ".proto";
@@ -71,7 +69,8 @@
     private boolean mStarted;
     private final Object mLock = new Object();
     private final DirectoryResolver mDirectoryResolver;
-    private final float mLowResScale;
+    private final float mLowResScaleFactor;
+    private boolean mEnableLowResSnapshots;
     private final boolean mUse16BitFormat;
 
     /**
@@ -83,13 +82,29 @@
 
     TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
         mDirectoryResolver = resolver;
+        final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+        final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
 
-        if (ActivityManager.isLowRamDeviceStatic()) {
-            mLowResScale = LOW_RAM_REDUCED_SCALE;
-        } else {
-            mLowResScale = service.mContext.getResources().getFloat(
-                    com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
+        if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("Low-res scale must be between 0 and 1");
         }
+        if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be between 0 and 1");
+        }
+        if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be greater than low-res scale");
+        }
+
+        if (lowResTaskSnapshotScale > 0) {
+            mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
+            setEnableLowResSnapshots(true);
+        } else {
+            mLowResScaleFactor = 0;
+            setEnableLowResSnapshots(false);
+        }
+
         mUse16BitFormat = service.mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
     }
@@ -155,13 +170,16 @@
         }
     }
 
+    boolean enableLowResSnapshots() {
+        return mEnableLowResSnapshots;
+    }
+
     /**
-     * Gets the scaling the persister uses for low resolution task snapshots.
-     *
-     * @return the lowResBitmap scale of task snapshots when they are set to be low res
+     * Not to be used. Only here for testing.
      */
-    float getLowResScale() {
-        return mLowResScale;
+    @VisibleForTesting
+    void setEnableLowResSnapshots(boolean enabled) {
+        mEnableLowResSnapshots = enabled;
     }
 
     /**
@@ -213,14 +231,10 @@
     }
 
     File getHighResolutionBitmapFile(int taskId, int userId) {
-        // Full sized bitmaps are disabled on low ram devices
-        if (DISABLE_HIGH_RES_BITMAPS) {
-            Slog.wtf(TAG, "This device does not support full sized resolution bitmaps.");
-            return null;
-        }
         return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
     }
 
+    @NonNull
     File getLowResolutionBitmapFile(int taskId, int userId) {
         return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
     }
@@ -234,11 +248,11 @@
         final File protoFile = getProtoFile(taskId, userId);
         final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
         protoFile.delete();
-        bitmapLowResFile.delete();
-
-        // Low ram devices do not have a full sized file to delete
-        if (!DISABLE_HIGH_RES_BITMAPS) {
-            final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
+        if (bitmapLowResFile.exists()) {
+            bitmapLowResFile.delete();
+        }
+        final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
+        if (bitmapFile.exists()) {
             bitmapFile.delete();
         }
     }
@@ -343,6 +357,8 @@
             final TaskSnapshotProto proto = new TaskSnapshotProto();
             proto.orientation = mSnapshot.getOrientation();
             proto.rotation = mSnapshot.getRotation();
+            proto.taskWidth = mSnapshot.getTaskSize().x;
+            proto.taskHeight = mSnapshot.getTaskSize().y;
             proto.insetLeft = mSnapshot.getContentInsets().left;
             proto.insetTop = mSnapshot.getContentInsets().top;
             proto.insetRight = mSnapshot.getContentInsets().right;
@@ -352,7 +368,6 @@
             proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
             proto.isTranslucent = mSnapshot.isTranslucent();
             proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
-            proto.scale = mSnapshot.getScale();
             proto.id = mSnapshot.getId();
             final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
             final File file = getProtoFile(mTaskId, mUserId);
@@ -379,11 +394,26 @@
             }
 
             final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
-            final Bitmap lowResBitmap = mSnapshot.isLowResolution()
-                    ? swBitmap
-                    : Bitmap.createScaledBitmap(swBitmap,
-                            (int) (bitmap.getWidth() * mLowResScale),
-                            (int) (bitmap.getHeight() * mLowResScale), true /* filter */);
+
+            final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
+            try {
+                FileOutputStream fos = new FileOutputStream(file);
+                swBitmap.compress(JPEG, QUALITY, fos);
+                fos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
+                return false;
+            }
+
+            if (!enableLowResSnapshots()) {
+                swBitmap.recycle();
+                return true;
+            }
+
+            final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
+                    (int) (bitmap.getWidth() * mLowResScaleFactor),
+                    (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
+            swBitmap.recycle();
 
             final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
             try {
@@ -396,22 +426,6 @@
             }
             lowResBitmap.recycle();
 
-            // For snapshots with lowResBitmap resolution, do not create or save full sized bitmaps
-            if (mSnapshot.isLowResolution()) {
-                swBitmap.recycle();
-                return true;
-            }
-
-            final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
-            try {
-                FileOutputStream fos = new FileOutputStream(file);
-                swBitmap.compress(JPEG, QUALITY, fos);
-                fos.close();
-            } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
-                return false;
-            }
-            swBitmap.recycle();
             return true;
         }
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index f4e4245..eb005e0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -36,6 +36,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+
 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getColorViewLeftInset;
@@ -53,9 +54,11 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -131,6 +134,8 @@
     private final Rect mContentInsets = new Rect();
     private final Rect mFrame = new Rect();
     private TaskSnapshot mSnapshot;
+    private final RectF mTmpSnapshotSize = new RectF();
+    private final RectF mTmpDstFrame = new RectF();
     private final CharSequence mTitle;
     private boolean mHasDrawn;
     private long mShownTime;
@@ -141,6 +146,8 @@
     @VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
     private final int mOrientationOnCreation;
     private final SurfaceControl.Transaction mTransaction;
+    private final Matrix mSnapshotMatrix = new Matrix();
+    private final float[] mTmpFloat9 = new float[9];
 
     static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity,
             TaskSnapshot snapshot) {
@@ -365,13 +372,17 @@
             frame = calculateSnapshotFrame(crop);
             mTransaction.setWindowCrop(mChildSurfaceControl, crop);
             mTransaction.setPosition(mChildSurfaceControl, frame.left, frame.top);
+            mTmpDstFrame.set(frame);
         } else {
             frame = null;
+            mTmpDstFrame.set(mFrame);
         }
 
         // Scale the mismatch dimensions to fill the task bounds
-        final float scale = 1 / mSnapshot.getScale();
-        mTransaction.setMatrix(mChildSurfaceControl, scale, 0, 0, scale);
+        mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
+        mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
+        mTransaction.setMatrix(mChildSurfaceControl, mSnapshotMatrix, mTmpFloat9);
+
         mTransaction.apply();
         surface.attachAndQueueBufferWithColorSpace(buffer, mSnapshot.getColorSpace());
         surface.release();
@@ -395,13 +406,17 @@
         rect.set(0, 0, mSnapshot.getSnapshot().getWidth(), mSnapshot.getSnapshot().getHeight());
         final Rect insets = mSnapshot.getContentInsets();
 
+        final float scaleX = (float) mSnapshot.getSnapshot().getWidth() / mSnapshot.getTaskSize().x;
+        final float scaleY =
+                (float) mSnapshot.getSnapshot().getHeight() / mSnapshot.getTaskSize().y;
+
         // Let's remove all system decorations except the status bar, but only if the task is at the
         // very top of the screen.
         final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
-        rect.inset((int) (insets.left * mSnapshot.getScale()),
-                isTop ? 0 : (int) (insets.top * mSnapshot.getScale()),
-                (int) (insets.right * mSnapshot.getScale()),
-                (int) (insets.bottom * mSnapshot.getScale()));
+        rect.inset((int) (insets.left * scaleX),
+                isTop ? 0 : (int) (insets.top * scaleY),
+                (int) (insets.right * scaleX),
+                (int) (insets.bottom * scaleY));
         return rect;
     }
 
@@ -412,14 +427,20 @@
      */
     @VisibleForTesting
     Rect calculateSnapshotFrame(Rect crop) {
-        final Rect frame = new Rect(crop);
-        final float scale = mSnapshot.getScale();
+        final float scaleX = (float) mSnapshot.getSnapshot().getWidth() / mSnapshot.getTaskSize().x;
+        final float scaleY =
+                (float) mSnapshot.getSnapshot().getHeight() / mSnapshot.getTaskSize().y;
 
         // Rescale the frame from snapshot to window coordinate space
-        frame.scale(1 / scale);
+        final Rect frame = new Rect(
+                (int) (crop.left / scaleX + 0.5f),
+                (int) (crop.top / scaleY + 0.5f),
+                (int) (crop.right / scaleX + 0.5f),
+                (int) (crop.bottom / scaleY + 0.5f)
+        );
 
         // By default, offset it to to top/left corner
-        frame.offsetTo((int) (-crop.left / scale), (int) (-crop.top / scale));
+        frame.offsetTo((int) (-crop.left / scaleX), (int) (-crop.top / scaleY));
 
         // However, we also need to make space for the navigation bar on the left side.
         final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left,
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 137d122..669eb78 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -669,8 +669,7 @@
      * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
      * the opening apps should be a wallpaper target.
      */
-    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<ActivityRecord> openingApps,
-            ArraySet<ActivityRecord> changingApps) {
+    void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<ActivityRecord> openingApps) {
         boolean adjust = false;
         if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
             adjust = true;
@@ -682,15 +681,6 @@
                     break;
                 }
             }
-            if (!adjust) {
-                for (int i = changingApps.size() - 1; i >= 0; --i) {
-                    final ActivityRecord activity = changingApps.valueAt(i);
-                    if (activity.windowsCanBeWallpaperTarget()) {
-                        adjust = true;
-                        break;
-                    }
-                }
-            }
         }
 
         if (adjust) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7a4d0b0..a0a70dc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -25,9 +25,13 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.os.UserHandle.USER_NULL;
 import static android.view.SurfaceControl.Transaction;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.IdentifierProto.TITLE;
+import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -64,6 +68,7 @@
 import android.view.DisplayInfo;
 import android.view.IWindowContainer;
 import android.view.MagnificationSpec;
+import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
@@ -94,7 +99,7 @@
  * changes are made to this class.
  */
 class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
-        implements Comparable<WindowContainer>, Animatable,
+        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
                    BLASTSyncEngine.TransactionReadyListener {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
@@ -169,6 +174,7 @@
      * Applied as part of the animation pass in "prepareSurfaces".
      */
     protected final SurfaceAnimator mSurfaceAnimator;
+    final SurfaceFreezer mSurfaceFreezer;
     protected final WindowManagerService mWmService;
 
     private final Point mTmpPos = new Point();
@@ -252,7 +258,6 @@
      * where it represents the starting-state snapshot.
      */
     WindowContainerThumbnail mThumbnail;
-    final Rect mTransitStartRect = new Rect();
     final Point mTmpPoint = new Point();
     protected final Rect mTmpRect = new Rect();
     final Rect mTmpPrevBounds = new Rect();
@@ -277,6 +282,7 @@
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
         mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
+        mSurfaceFreezer = new SurfaceFreezer(this, wms);
     }
 
     @Override
@@ -524,13 +530,6 @@
 
         if (mSurfaceControl != null) {
             getPendingTransaction().remove(mSurfaceControl);
-
-            // Merge to parent transaction to ensure the transactions on this WindowContainer are
-            // applied in native even if WindowContainer is removed.
-            if (mParent != null) {
-                mParent.getPendingTransaction().merge(getPendingTransaction());
-            }
-
             setSurfaceControl(null);
             mLastSurfacePosition.set(0, 0);
             scheduleAnimation();
@@ -841,7 +840,7 @@
      * @return {@code true} if the container is in changing app transition.
      */
     boolean isChangingAppTransition() {
-        return false;
+        return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this);
     }
 
     void sendAppVisibilityToClients() {
@@ -893,6 +892,31 @@
     }
 
     /**
+     * Called when the visibility of a child is asked to change. This is before visibility actually
+     * changes (eg. a transition animation might play out first).
+     */
+    void onChildVisibilityRequested(boolean visible) {
+        // If we are changing visibility, then a snapshot isn't necessary and we are no-longer
+        // part of a change transition.
+        mSurfaceFreezer.unfreeze(getPendingTransaction());
+        if (mDisplayContent != null) {
+            mDisplayContent.mChangingContainers.remove(this);
+        }
+        WindowContainer parent = getParent();
+        if (parent != null) {
+            parent.onChildVisibilityRequested(visible);
+        }
+    }
+
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(USER_ID, USER_NULL);
+        proto.write(TITLE, "WindowContainer");
+        proto.end(token);
+    }
+
+    /**
      * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
      * this will not be focusable either.
      */
@@ -1924,7 +1948,8 @@
 
         // TODO: This should use isVisible() but because isVisible has a really weird meaning at
         // the moment this doesn't work for all animatable window containers.
-        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback);
+        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
+                mSurfaceFreezer);
     }
 
     void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -1941,6 +1966,11 @@
     }
 
     @Override
+    public SurfaceControl getFreezeSnapshotTarget() {
+        return null;
+    }
+
+    @Override
     public Builder makeAnimationLeash() {
         return makeSurface().setContainerLayer();
     }
@@ -2009,8 +2039,9 @@
                         getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                     }
                     if (thumbnailAdapter != null) {
-                        mThumbnail.startAnimation(
-                                getPendingTransaction(), thumbnailAdapter, !isVisible());
+                        mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
+                                thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION,
+                                (type, anim) -> { });
                     }
                 }
             } else {
@@ -2056,7 +2087,7 @@
         if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
             final RemoteAnimationController.RemoteAnimationRecord adapters =
                     controller.createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
-                            (isChanging ? mTransitStartRect : null));
+                            (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
             resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
         } else if (isChanging) {
             final float durationScale = mWmService.getTransitionAnimationScaleLocked();
@@ -2064,14 +2095,15 @@
             mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
 
             final AnimationAdapter adapter = new LocalAnimationAdapter(
-                    new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, displayInfo,
-                            durationScale, true /* isAppAnimation */, false /* isThumbnail */),
+                    new WindowChangeAnimationSpec(mSurfaceFreezer.mFreezeBounds, mTmpRect,
+                            displayInfo, durationScale, true /* isAppAnimation */,
+                            false /* isThumbnail */),
                     getSurfaceAnimationRunner());
 
-            final AnimationAdapter thumbnailAdapter = mThumbnail != null
-                    ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(mTransitStartRect,
-                    mTmpRect, displayInfo, durationScale, true /* isAppAnimation */,
-                    true /* isThumbnail */), getSurfaceAnimationRunner())
+            final AnimationAdapter thumbnailAdapter = mSurfaceFreezer.mSnapshot != null
+                    ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(
+                    mSurfaceFreezer.mFreezeBounds, mTmpRect, displayInfo, durationScale,
+                    true /* isAppAnimation */, true /* isThumbnail */), getSurfaceAnimationRunner())
                     : null;
             resultAdapters = new Pair<>(adapter, thumbnailAdapter);
             mTransit = transit;
@@ -2189,6 +2221,7 @@
     @Override
     public void onAnimationLeashLost(Transaction t) {
         mLastLayer = -1;
+        mSurfaceFreezer.unfreeze(t);
         reassignLayer(t);
     }
 
@@ -2329,6 +2362,10 @@
         mSurfaceControl = sc;
     }
 
+    RemoteAnimationDefinition getRemoteAnimationDefinition() {
+        return null;
+    }
+
     /** Cheap way of doing cast and instanceof. */
     Task asTask() {
         return null;
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index 35d245f..3e4c992 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.LocusId;
+import android.os.FileUtils;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 
@@ -251,5 +252,6 @@
     void onDestroy() {
         mEventStore.onDestroy();
         mConversationStore.onDestroy();
+        FileUtils.deleteContentsAndDir(mPackageDataDir);
     }
 }
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index d2f86ee..602e4e1 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -26,6 +26,8 @@
         "services.core",
         "services.net",
     ],
+
+    libs: ["ike-stubs"],
 }
 
 //##################################################################
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index ef0ca66..5160eae 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -28,6 +28,8 @@
         "services.core",
         "services.net",
     ],
+
+    libs: ["ike-stubs"],
 }
 
 //##################################################################
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 feae1e1..08bd1ee 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -144,4 +144,49 @@
         Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
         Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
     }
+
+    @Test
+    public void testBrightnessHasLowerPriorityThanUser() {
+        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
+        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
+
+        int displayId = 0;
+        DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(displayId, votes);
+        votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
+        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
+        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
+        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60));
+        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+        Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+        Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 30ab9cd..dc30add 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -117,7 +117,7 @@
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyVibration_WithAccessibilityEnabled() {
-        mBatterySaverPolicy.setAccessibilityEnabledForTest(true);
+        mBatterySaverPolicy.setAccessibilityEnabled(true);
         testServiceDefaultValue_Off(ServiceType.VIBRATION);
     }
 
@@ -339,4 +339,57 @@
                 Policy.fromSettings(BATTERY_SAVER_CONSTANTS, ""));
         verifyBatterySaverConstantsUpdated();
     }
+
+    public void testCarModeChanges_Full() {
+        mBatterySaverPolicy.updateConstantsLocked(
+                "gps_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
+                        + ",enable_night_mode=true", "");
+        mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_FULL);
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+        assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+
+        mBatterySaverPolicy.setCarModeEnabled(true);
+
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isAnyOf(PowerManager.LOCATION_MODE_NO_CHANGE,
+                        PowerManager.LOCATION_MODE_FOREGROUND_ONLY);
+        assertFalse(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+
+        mBatterySaverPolicy.setCarModeEnabled(false);
+
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+        assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+    }
+
+    public void testCarModeChanges_Adaptive() {
+        mBatterySaverPolicy.setAdaptivePolicyLocked(
+                Policy.fromSettings(
+                        "gps_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
+                                + ",enable_night_mode=true", ""));
+        mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_ADAPTIVE);
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+        assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+
+        mBatterySaverPolicy.setCarModeEnabled(true);
+
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isAnyOf(PowerManager.LOCATION_MODE_NO_CHANGE,
+                        PowerManager.LOCATION_MODE_FOREGROUND_ONLY);
+        assertFalse(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+
+        mBatterySaverPolicy.setCarModeEnabled(false);
+
+        assertThat(mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION).locationMode)
+                .isEqualTo(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+        assertTrue(mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.NIGHT_MODE).batterySaverEnabled);
+    }
 }
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index dab0a5f..767857b 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -23,7 +23,6 @@
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d0283f7..29b0df5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -153,7 +153,6 @@
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 
-import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
@@ -360,7 +359,7 @@
     @Before
     public void setUp() throws Exception {
         // Shell permisssions will override permissions of our app, so add all necessary permissions
-        // fo this test here:
+        // for this test here:
         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 "android.permission.WRITE_DEVICE_CONFIG",
                 "android.permission.READ_DEVICE_CONFIG",
@@ -6089,7 +6088,9 @@
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
-        shortcutInfos.add(mock(ShortcutInfo.class));
+        ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.isLongLived()).thenReturn(true);
+        shortcutInfos.add(info);
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
 
         // Test: Send the bubble notification
@@ -6116,7 +6117,8 @@
         verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
 
         // We're no longer a bubble
-        Notification notif2 = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
+        Notification notif2 = mService.getNotificationRecord(
+                nr.getSbn().getKey()).getNotification();
         assertFalse(notif2.isBubbleNotification());
     }
 
@@ -6409,11 +6411,6 @@
         convos.add(convo2);
         when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
 
-        // only one valid shortcut
-        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
-                .setPackage(PKG_P)
-                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
-                .setShortcutIds(Arrays.asList(channel1.getConversationId()));
         ShortcutInfo si = mock(ShortcutInfo.class);
         when(si.getShortLabel()).thenReturn("Hello");
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 2d4b5a7..00b9273 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -51,6 +51,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
@@ -203,16 +204,13 @@
         return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
-    private StatusBarNotification getMessagingStyleNotification(@Nullable String shortcutId) {
+    private StatusBarNotification getMessagingStyleNotification() {
         final Builder builder = new Builder(mMockContext)
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
         Person person = new Person.Builder().setName("Bob").build();
         builder.setStyle(new Notification.MessagingStyle(person));
-        if (shortcutId != null) {
-            builder.setShortcutId(shortcutId);
-        }
 
         Notification n = builder.build();
         return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
@@ -1122,16 +1120,18 @@
 
     @Test
     public void testIsConversation() {
-        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        StatusBarNotification sbn = getMessagingStyleNotification();
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(mock(ShortcutInfo.class));
 
         assertTrue(record.isConversation());
     }
 
     @Test
-    public void testIsConversation_nullShortcutId() {
-        StatusBarNotification sbn = getMessagingStyleNotification(null);
+    public void testIsConversation_nullShortcut() {
+        StatusBarNotification sbn = getMessagingStyleNotification();
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(null);
 
         assertFalse(record.isConversation());
     }
@@ -1140,25 +1140,28 @@
     public void testIsConversation_bypassShortcutFlagEnabled() {
         Settings.Global.putString(mContentResolver,
                 FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
-        StatusBarNotification sbn = getMessagingStyleNotification(null);
+        StatusBarNotification sbn = getMessagingStyleNotification();
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(null);
 
         assertTrue(record.isConversation());
     }
 
     @Test
     public void testIsConversation_channelDemoted() {
-        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        StatusBarNotification sbn = getMessagingStyleNotification();
         channel.setDemoted(true);
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(mock(ShortcutInfo.class));
 
         assertFalse(record.isConversation());
     }
 
     @Test
     public void testIsConversation_withAdjustmentOverride() {
-        StatusBarNotification sbn = getMessagingStyleNotification("test_shortcut_id");
+        StatusBarNotification sbn = getMessagingStyleNotification();
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.setShortcutInfo(mock(ShortcutInfo.class));
 
         Bundle bundle = new Bundle();
         bundle.putBoolean(KEY_NOT_CONVERSATION, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 27f72a1..e4d50c0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -55,6 +55,8 @@
 import android.util.AtomicFile;
 import android.util.Pair;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
 import com.android.server.LocalServices;
@@ -65,6 +67,7 @@
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -116,6 +119,10 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        // Shell permisssions will override permissions of our app, so add all necessary permissions
+        // for this test here:
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                "android.permission.READ_CONTACTS");
 
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class));
@@ -153,6 +160,12 @@
         mService.setPreferencesHelper(mPreferencesHelper);
     }
 
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().dropShellPermissionIdentity();
+    }
+
     @Test
     public void testInit() throws Exception {
         List<String> dialer0 = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 7204a81..e1ce431f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -255,7 +255,7 @@
     }
 
     private void notifyTransitionStarting(ActivityRecord activity) {
-        final ArrayMap<ActivityRecord, Integer> reasons = new ArrayMap<>();
+        final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
         reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN);
         mActivityMetricsLogger.notifyTransitionStarting(reasons);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 8c2b293..4cb50c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -103,12 +103,12 @@
         setUpOnDisplay(mDisplayContent);
 
         mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        assertEquals(1, mDisplayContent.mChangingApps.size());
+        assertEquals(1, mDisplayContent.mChangingContainers.size());
 
         // Verify we are in a change transition, but without a snapshot.
         // Though, the test will actually have crashed by now if a snapshot is attempted.
-        assertNull(mActivity.getThumbnail());
-        assertTrue(mActivity.isInChangeTransition());
+        assertNull(mTask.mSurfaceFreezer.mSnapshot);
+        assertTrue(mTask.isInChangeTransition());
 
         waitUntilHandlersIdle();
         mActivity.removeImmediately();
@@ -121,14 +121,14 @@
         setUpOnDisplay(mDisplayContent);
 
         mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        assertEquals(1, mDisplayContent.mChangingApps.size());
-        assertTrue(mActivity.isInChangeTransition());
+        assertEquals(1, mDisplayContent.mChangingContainers.size());
+        assertTrue(mTask.isInChangeTransition());
 
         // Removing the app-token from the display should clean-up the
         // the change leash.
         mDisplayContent.removeAppToken(mActivity.token);
-        assertEquals(0, mDisplayContent.mChangingApps.size());
-        assertFalse(mActivity.isInChangeTransition());
+        assertEquals(0, mDisplayContent.mChangingContainers.size());
+        assertFalse(mTask.isInChangeTransition());
 
         waitUntilHandlersIdle();
         mActivity.removeImmediately();
@@ -152,8 +152,8 @@
         assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
 
         // Make sure we're not waiting for a change animation (no leash)
-        assertFalse(mActivity.isInChangeTransition());
-        assertNull(mActivity.getThumbnail());
+        assertFalse(mTask.isInChangeTransition());
+        assertNull(mActivity.mSurfaceFreezer.mSnapshot);
 
         waitUntilHandlersIdle();
         mActivity.removeImmediately();
@@ -165,13 +165,13 @@
         setUpOnDisplay(mDisplayContent);
 
         mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        assertEquals(1, mDisplayContent.mChangingApps.size());
-        assertTrue(mActivity.isInChangeTransition());
+        assertEquals(1, mDisplayContent.mChangingContainers.size());
+        assertTrue(mTask.isInChangeTransition());
 
         // Changing visibility should cancel the change transition and become closing
         mActivity.setVisibility(false, false);
-        assertEquals(0, mDisplayContent.mChangingApps.size());
-        assertFalse(mActivity.isInChangeTransition());
+        assertEquals(0, mDisplayContent.mChangingContainers.size());
+        assertFalse(mTask.isInChangeTransition());
 
         waitUntilHandlersIdle();
         mActivity.removeImmediately();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 3a724a1..c7f94ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -247,7 +247,7 @@
     @Test
     public void testChange() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
-        mDisplayContent.mChangingApps.add(win.mActivityRecord);
+        mDisplayContent.mChangingContainers.add(win.mActivityRecord);
         try {
             final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
                     win.mActivityRecord, new Point(50, 100), new Rect(50, 100, 150, 150),
@@ -290,7 +290,7 @@
             verify(mThumbnailFinishedCallback).onAnimationFinished(
                     eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
         } finally {
-            mDisplayContent.mChangingApps.clear();
+            mDisplayContent.mChangingContainers.clear();
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index ec20262..71d3194 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -52,7 +52,6 @@
 import static org.mockito.ArgumentMatchers.refEq;
 
 import android.app.ActivityOptions;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -517,7 +516,7 @@
      */
     @Test
     public void testStartHomeOnAllDisplays() {
-        mockResolveHomeActivity();
+        mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
         mockResolveSecondaryHomeActivity();
 
         // Create secondary displays.
@@ -644,30 +643,26 @@
     }
 
     /**
-     * Tests that secondary home should be selected if default home not set.
+     * Tests that secondary home should be selected if primary home not set.
      */
     @Test
-    public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSet() {
-        final Intent defaultHomeIntent = mService.getHomeIntent();
-        final ActivityInfo aInfoDefault = new ActivityInfo();
-        aInfoDefault.name = ResolverActivity.class.getName();
-        doReturn(aInfoDefault).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
-                refEq(defaultHomeIntent));
+    public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() {
+        // Setup: primary home not set.
+        final Intent primaryHomeIntent = mService.getHomeIntent();
+        final ActivityInfo aInfoPrimary = new ActivityInfo();
+        aInfoPrimary.name = ResolverActivity.class.getName();
+        doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
+                refEq(primaryHomeIntent));
+        // Setup: set secondary home.
+        mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
 
-        final String secondaryHomeComponent = mService.mContext.getResources().getString(
-                com.android.internal.R.string.config_secondaryHomeComponent);
-        final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent);
-        final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null);
-        final ActivityInfo aInfoSecondary = new ActivityInfo();
-        aInfoSecondary.name = comp.getClassName();
-        doReturn(aInfoSecondary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
-                refEq(secondaryHomeIntent));
-
-        // Should fallback to secondary home if default home not set.
+        // Run the test.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
                 .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
-
-        assertEquals(comp.getClassName(), resolvedInfo.first.name);
+        final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+        assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+        assertEquals(aInfoSecondary.applicationInfo.packageName,
+                resolvedInfo.first.applicationInfo.packageName);
     }
 
     /**
@@ -675,103 +670,60 @@
      * config_useSystemProvidedLauncherForSecondary.
      */
     @Test
-    public void testResolveSecondaryHomeActivityForced() throws Exception {
-        Resources resources = mContext.getResources();
-        spyOn(resources);
-        try {
-            // setUp: set secondary launcher and force it.
-            final String defaultSecondaryHome =
-                    "com.android.test/com.android.test.TestDefaultSecondaryHome";
-            final ComponentName secondaryComp = ComponentName.unflattenFromString(
-                    defaultSecondaryHome);
-            doReturn(defaultSecondaryHome).when(resources).getString(
-                    com.android.internal.R.string.config_secondaryHomeComponent);
-            doReturn(true).when(resources).getBoolean(
-                    com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
-            final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null);
-            assertEquals(secondaryComp, secondaryHomeIntent.getComponent());
-            final ActivityInfo aInfoSecondary = new ActivityInfo();
-            aInfoSecondary.name = secondaryComp.getClassName();
-            aInfoSecondary.applicationInfo = new ApplicationInfo();
-            aInfoSecondary.applicationInfo.packageName = secondaryComp.getPackageName();
-            doReturn(aInfoSecondary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
-                    refEq(secondaryHomeIntent));
-            final Intent homeIntent = mService.getHomeIntent();
-            final ActivityInfo aInfoDefault = new ActivityInfo();
-            aInfoDefault.name = "fakeHomeActivity";
-            aInfoDefault.applicationInfo = new ApplicationInfo();
-            aInfoDefault.applicationInfo.packageName = "fakeHomePackage";
-            doReturn(aInfoDefault).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
-                    refEq(homeIntent));
-            // Let resolveActivities call to validate both main launcher and second launcher so that
-            // resolveActivities call does not work as enabler for secondary.
-            final List<ResolveInfo> resolutions1 = new ArrayList<>();
-            final ResolveInfo resolveInfo1 = new ResolveInfo();
-            resolveInfo1.activityInfo = new ActivityInfo();
-            resolveInfo1.activityInfo.name = aInfoDefault.name;
-            resolveInfo1.activityInfo.applicationInfo = aInfoDefault.applicationInfo;
-            resolutions1.add(resolveInfo1);
-            doReturn(resolutions1).when(mRootWindowContainer).resolveActivities(anyInt(),
-                    refEq(homeIntent));
-            final List<ResolveInfo> resolutions2 = new ArrayList<>();
-            final ResolveInfo resolveInfo2 = new ResolveInfo();
-            resolveInfo2.activityInfo = new ActivityInfo();
-            resolveInfo2.activityInfo.name = aInfoSecondary.name;
-            resolveInfo2.activityInfo.applicationInfo = aInfoSecondary.applicationInfo;
-            resolutions2.add(resolveInfo2);
-            doReturn(resolutions2).when(mRootWindowContainer).resolveActivities(anyInt(),
-                    refEq(secondaryHomeIntent));
-            doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
-                    any(), anyInt(), anyBoolean());
-
-            // Run the test
-            final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
-                    .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
-            assertEquals(secondaryComp.getClassName(), resolvedInfo.first.name);
-            assertEquals(secondaryComp.getPackageName(),
-                    resolvedInfo.first.applicationInfo.packageName);
-            assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
-        } finally {
-            // tearDown
-            reset(resources);
-        }
-    }
-
-    /**
-     * Tests that secondary home should be selected if default home not support secondary displays
-     * or there is no matched activity in the same package as selected default home.
-     */
-    @Test
-    public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSupportMultiDisplay() {
-        mockResolveHomeActivity();
-
+    public void testResolveSecondaryHomeActivityForced() {
+        // SetUp: set primary home.
+        mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+        // SetUp: set secondary home and force it.
+        mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */);
+        final Intent secondaryHomeIntent =
+                mService.getSecondaryHomeIntent(null /* preferredPackage */);
         final List<ResolveInfo> resolutions = new ArrayList<>();
-        doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
-
-        final String secondaryHomeComponent = mService.mContext.getResources().getString(
-                com.android.internal.R.string.config_secondaryHomeComponent);
-        final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent);
-        final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null);
-        final ActivityInfo aInfoSecondary = new ActivityInfo();
-        aInfoSecondary.name = comp.getClassName();
-        doReturn(aInfoSecondary).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+        resolveInfo.activityInfo = aInfoSecondary;
+        resolutions.add(resolveInfo);
+        doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(),
                 refEq(secondaryHomeIntent));
+        doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
+                any(), anyInt(), anyBoolean());
 
-        // Should fallback to secondary home if selected default home not support secondary displays
-        // or there is no matched activity in the same package as selected default home.
+        // Run the test.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
                 .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
-
-        assertEquals(comp.getClassName(), resolvedInfo.first.name);
+        assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+        assertEquals(aInfoSecondary.applicationInfo.packageName,
+                resolvedInfo.first.applicationInfo.packageName);
     }
 
     /**
-     * Tests that default home activity should be selected if it already support secondary displays.
+     * Tests that secondary home should be selected if primary home not support secondary displays
+     * or there is no matched activity in the same package as selected primary home.
      */
     @Test
-    public void testResolveSecondaryHomeActivityWhenDefaultHomeSupportMultiDisplay() {
-        final ActivityInfo aInfoDefault = mockResolveHomeActivity();
+    public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSupportMultiDisplay() {
+        // Setup: there is no matched activity in the same package as selected primary home.
+        mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+        final List<ResolveInfo> resolutions = new ArrayList<>();
+        doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
+        // Setup: set secondary home.
+        mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */);
 
+        // Run the test.
+        final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
+                .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
+        final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/);
+        assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
+        assertEquals(aInfoSecondary.applicationInfo.packageName,
+                resolvedInfo.first.applicationInfo.packageName);
+    }
+    /**
+     * Tests that primary home activity should be selected if it already support secondary displays.
+     */
+    @Test
+    public void testResolveSecondaryHomeActivityWhenPrimaryHomeSupportMultiDisplay() {
+        // SetUp: set primary home.
+        mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+        // SetUp: put primary home info on 2nd item
         final List<ResolveInfo> resolutions = new ArrayList<>();
         final ResolveInfo infoFake1 = new ResolveInfo();
         infoFake1.activityInfo = new ActivityInfo();
@@ -779,7 +731,8 @@
         infoFake1.activityInfo.applicationInfo = new ApplicationInfo();
         infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1";
         final ResolveInfo infoFake2 = new ResolveInfo();
-        infoFake2.activityInfo = aInfoDefault;
+        final ActivityInfo aInfoPrimary = getFakeHomeActivityInfo(true /* primaryHome */);
+        infoFake2.activityInfo = aInfoPrimary;
         resolutions.add(infoFake1);
         resolutions.add(infoFake2);
         doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any());
@@ -787,13 +740,12 @@
         doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
                 any(), anyInt(), anyBoolean());
 
-        // Use default home activity if it support secondary displays.
+        // Run the test.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
                 .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
-
-        assertEquals(aInfoDefault.applicationInfo.packageName,
+        assertEquals(aInfoPrimary.name, resolvedInfo.first.name);
+        assertEquals(aInfoPrimary.applicationInfo.packageName,
                 resolvedInfo.first.applicationInfo.packageName);
-        assertEquals(aInfoDefault.name, resolvedInfo.first.name);
     }
 
     /**
@@ -801,8 +753,9 @@
      */
     @Test
     public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() {
-        mockResolveHomeActivity();
-
+        // SetUp: set primary home.
+        mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */);
+        // Setup: prepare two eligible activity info.
         final List<ResolveInfo> resolutions = new ArrayList<>();
         final ResolveInfo infoFake1 = new ResolveInfo();
         infoFake1.activityInfo = new ActivityInfo();
@@ -821,7 +774,7 @@
         doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplay(
                 any(), anyInt(), anyBoolean());
 
-        // Use the first one of matched activities in the same package as selected default home.
+        // Use the first one of matched activities in the same package as selected primary home.
         final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer
                 .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
 
@@ -884,32 +837,48 @@
 
     /**
      * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
-     * info for test cases (the original implementation will resolve from the real package manager).
+     * info for test cases.
+     *
+     * @param primaryHome Indicate to use primary home intent as parameter, otherwise, use
+     *                    secondary home intent.
+     * @param forceSystemProvided Indicate to force using system provided home activity.
      */
-    private ActivityInfo mockResolveHomeActivity() {
-        final Intent homeIntent = mService.getHomeIntent();
-        final ActivityInfo aInfoDefault = new ActivityInfo();
-        aInfoDefault.name = "fakeHomeActivity";
-        aInfoDefault.applicationInfo = new ApplicationInfo();
-        aInfoDefault.applicationInfo.packageName = "fakeHomePackage";
-        doReturn(aInfoDefault).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
-                refEq(homeIntent));
-        return aInfoDefault;
+    private void mockResolveHomeActivity(boolean primaryHome, boolean forceSystemProvided) {
+        ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome);
+        Intent targetIntent;
+        if (primaryHome) {
+            targetIntent = mService.getHomeIntent();
+        } else {
+            Resources resources = mContext.getResources();
+            spyOn(resources);
+            doReturn(targetActivityInfo.applicationInfo.packageName).when(resources).getString(
+                    com.android.internal.R.string.config_secondaryHomePackage);
+            doReturn(forceSystemProvided).when(resources).getBoolean(
+                    com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
+            targetIntent = mService.getSecondaryHomeIntent(null /* preferredPackage */);
+        }
+        doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(),
+                refEq(targetIntent));
     }
 
     /**
      * Mock {@link RootWindowContainer#resolveSecondaryHomeActivity} for returning consistent
-     * activity info for test cases (the original implementation will resolve from the real package
-     * manager).
+     * activity info for test cases.
      */
     private void mockResolveSecondaryHomeActivity() {
         final Intent secondaryHomeIntent = mService
                 .getSecondaryHomeIntent(null /* preferredPackage */);
-        final ActivityInfo aInfoSecondary = new ActivityInfo();
-        aInfoSecondary.name = "fakeSecondaryHomeActivity";
-        aInfoSecondary.applicationInfo = new ApplicationInfo();
-        aInfoSecondary.applicationInfo.packageName = "fakeSecondaryHomePackage";
+        final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false);
         doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer)
                 .resolveSecondaryHomeActivity(anyInt(), anyInt());
     }
+
+    private ActivityInfo getFakeHomeActivityInfo(boolean primaryHome) {
+        final ActivityInfo aInfo = new ActivityInfo();
+        aInfo.name = primaryHome ? "fakeHomeActivity" : "fakeSecondaryHomeActivity";
+        aInfo.applicationInfo = new ApplicationInfo();
+        aInfo.applicationInfo.packageName =
+                primaryHome ? "fakeHomePackage" : "fakeSecondaryHomePackage";
+        return  aInfo;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index bd8aacb..20d9aff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -36,6 +37,7 @@
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
@@ -138,6 +140,7 @@
         final int orientation = Configuration.ORIENTATION_PORTRAIT;
         final float scaleFraction = 0.25f;
         final Rect contentInsets = new Rect(1, 2, 3, 4);
+        final Point taskSize = new Point(5, 6);
 
         try {
             ActivityManager.TaskSnapshot.Builder builder =
@@ -147,14 +150,13 @@
             builder.setSystemUiVisibility(systemUiVisibility);
             builder.setWindowingMode(windowingMode);
             builder.setColorSpace(sRGB);
-            builder.setIsLowResolution(true);
             builder.setOrientation(orientation);
             builder.setContentInsets(contentInsets);
             builder.setIsTranslucent(true);
-            builder.setScaleFraction(0.25f);
             builder.setSnapshot(buffer);
             builder.setIsRealSnapshot(true);
             builder.setPixelFormat(pixelFormat);
+            builder.setTaskSize(taskSize);
 
             // Not part of TaskSnapshot itself, used in screenshot process
             assertEquals(pixelFormat, builder.getPixelFormat());
@@ -165,13 +167,15 @@
             assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
             assertEquals(windowingMode, snapshot.getWindowingMode());
             assertEquals(sRGB, snapshot.getColorSpace());
-            assertTrue(snapshot.isLowResolution());
+            // Snapshots created with the Builder class are always high-res. The only way to get a
+            // low-res snapshot is to load it from the disk in TaskSnapshotLoader.
+            assertFalse(snapshot.isLowResolution());
             assertEquals(orientation, snapshot.getOrientation());
             assertEquals(contentInsets, snapshot.getContentInsets());
             assertTrue(snapshot.isTranslucent());
-            assertEquals(scaleFraction, builder.getScaleFraction(), 0.001f);
             assertSame(buffer, snapshot.getSnapshot());
             assertTrue(snapshot.isRealSnapshot());
+            assertEquals(taskSize, snapshot.getTaskSize());
         } finally {
             if (buffer != null) {
                 buffer.destroy();
@@ -188,11 +192,9 @@
 
         final ActivityManager.TaskSnapshot.Builder builder =
                 new ActivityManager.TaskSnapshot.Builder();
-        final float scaleFraction = 0.8f;
         mWm.mTaskSnapshotController.prepareTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
-                scaleFraction, PixelFormat.UNKNOWN, builder);
+                PixelFormat.UNKNOWN, builder);
 
-        assertEquals(scaleFraction, builder.getScaleFraction(), 0 /* delta */);
         // The pixel format should be selected automatically.
         assertNotEquals(PixelFormat.UNKNOWN, builder.getPixelFormat());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 0b16e5c..40f15b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -19,12 +19,16 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -36,10 +40,12 @@
 
 import androidx.test.filters.MediumTest;
 
+import com.android.server.wm.TaskSnapshotLoader.PreRLegacySnapshotConfig;
 import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
 
 import java.io.File;
 import java.util.function.Predicate;
@@ -55,6 +61,8 @@
 @RunWith(WindowTestRunner.class)
 public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
 
+    private static final float DELTA = 0.00001f;
+
     private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
 
     @Test
@@ -148,29 +156,172 @@
     }
 
     @Test
-    public void testLowResolutionPersistAndLoadSnapshot() {
+    public void testLegacyPLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(true);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any P low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = false;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, 0.6f, DELTA);
+        assertTrue(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.6f, DELTA);
+        assertTrue(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyPNonLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(false);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any O device, or a P non-low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, 1.0f, DELTA);
+        assertFalse(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.5f, DELTA);
+        assertFalse(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyQLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(true);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any Q low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0.6f;
+        final boolean hasHighResFile = false;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, legacyScale, DELTA);
+        assertEquals(highResConf.mScale, 0.6f, DELTA);
+        assertTrue(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, legacyScale, DELTA);
+        assertEquals(lowResConf.mScale, 0.6f, DELTA);
+        assertTrue(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testLegacyQNonLowRamConfig() throws Exception {
+        MockitoSession mockSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+
+        when(ActivityManager.isLowRamDeviceStatic()).thenReturn(false);
+
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any Q non-low_ram device
+        final int taskWidth = 0;
+        final float legacyScale = 0.8f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNotNull(highResConf);
+        assertEquals(highResConf.mScale, legacyScale, DELTA);
+        assertEquals(highResConf.mScale, 0.8f, DELTA);
+        assertFalse(highResConf.mForceLoadReducedJpeg);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNotNull(lowResConf);
+        assertEquals(lowResConf.mScale, 0.5f * legacyScale, DELTA);
+        assertEquals(lowResConf.mScale, 0.5f * 0.8f, DELTA);
+        assertFalse(lowResConf.mForceLoadReducedJpeg);
+
+        mockSession.finishMocking();
+    }
+
+    @Test
+    public void testNonLegacyRConfig() throws Exception {
+        // taskWidth and legacyScale as would be defined in the proto, and presence of a *.jpg file,
+        // for any R device
+        final int taskWidth = 1440;
+        final float legacyScale = 0f;
+        final boolean hasHighResFile = true;
+
+        PreRLegacySnapshotConfig highResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, false /* loadLowResolutionBitmap */);
+        assertNull(highResConf);
+
+        PreRLegacySnapshotConfig lowResConf = mLoader.getLegacySnapshotConfig(
+                taskWidth, legacyScale, hasHighResFile, true /* loadLowResolutionBitmap */);
+        assertNull(lowResConf);
+    }
+
+    @Test
+    public void testDisabledLowResolutionPersistAndLoadSnapshot() {
+        mPersister.setEnableLowResSnapshots(false);
+
         TaskSnapshot a = new TaskSnapshotBuilder()
-                .setScale(0.5f)
+                .setScaleFraction(0.5f)
                 .setIsLowResolution(true)
                 .build();
         assertTrue(a.isLowResolution());
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.waitForQueueEmpty();
         final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
-                new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
+                new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
         final File[] nonExistsFiles = new File[]{
-                new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
+                new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"),
         };
         assertTrueForFiles(files, File::exists, " must exist");
         assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
-        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, true /* isLowResolution */);
+        final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */);
         assertNotNull(snapshot);
         assertEquals(TEST_INSETS, snapshot.getContentInsets());
         assertNotNull(snapshot.getSnapshot());
         assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
 
         final TaskSnapshot snapshotNotExist = mLoader.loadTask(1, mTestUserId,
-                false /* isLowResolution */);
+                true /* isLowResolution */);
         assertNull(snapshotNotExist);
     }
 
@@ -271,13 +422,11 @@
     @Test
     public void testScalePersistAndLoadSnapshot() {
         TaskSnapshot a = new TaskSnapshotBuilder()
-                .setScale(0.25f)
+                .setScaleFraction(0.25f)
                 .build();
         TaskSnapshot b = new TaskSnapshotBuilder()
-                .setScale(0.75f)
+                .setScaleFraction(0.75f)
                 .build();
-        assertEquals(0.25f, a.getScale(), 1E-5);
-        assertEquals(0.75f, b.getScale(), 1E-5);
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
         mPersister.waitForQueueEmpty();
@@ -287,8 +436,6 @@
                 false /* isLowResolution */);
         assertNotNull(snapshotA);
         assertNotNull(snapshotB);
-        assertEquals(0.25f, snapshotA.getScale(), 1E-5);
-        assertEquals(0.75f, snapshotB.getScale(), 1E-5);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 4612dba..fa6663c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -30,6 +30,7 @@
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.UserManager;
 import android.view.Surface;
@@ -87,8 +88,10 @@
      * Builds a TaskSnapshot.
      */
     static class TaskSnapshotBuilder {
+        private static final int SNAPSHOT_WIDTH = 100;
+        private static final int SNAPSHOT_HEIGHT = 100;
 
-        private float mScale = 1f;
+        private float mScaleFraction = 1f;
         private boolean mIsLowResolution = false;
         private boolean mIsRealSnapshot = true;
         private boolean mIsTranslucent = false;
@@ -96,8 +99,11 @@
         private int mSystemUiVisibility = 0;
         private int mRotation = Surface.ROTATION_0;
 
-        TaskSnapshotBuilder setScale(float scale) {
-            mScale = scale;
+        TaskSnapshotBuilder() {
+        }
+
+        TaskSnapshotBuilder setScaleFraction(float scale) {
+            mScaleFraction = scale;
             return this;
         }
 
@@ -132,15 +138,20 @@
         }
 
         TaskSnapshot build() {
-            final GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
+            // To satisfy existing tests, ensure the graphics buffer is always 100x100, and
+            // compute the ize of the task according to mScaleFraction.
+            Point taskSize = new Point((int) (SNAPSHOT_WIDTH / mScaleFraction),
+                    (int) (SNAPSHOT_HEIGHT / mScaleFraction));
+            final GraphicBuffer buffer = GraphicBuffer.create(SNAPSHOT_WIDTH, SNAPSHOT_HEIGHT,
+                    PixelFormat.RGBA_8888,
                     USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
             Canvas c = buffer.lockCanvas();
             c.drawColor(Color.RED);
             buffer.unlockCanvasAndPost(c);
             return new TaskSnapshot(MOCK_SNAPSHOT_ID, new ComponentName("", ""), buffer,
                     ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
-                    mRotation, TEST_INSETS,
-                    mIsLowResolution, mScale, mIsRealSnapshot,
+                    mRotation, taskSize, TEST_INSETS,
+                    mIsLowResolution, mIsRealSnapshot,
                     mWindowingMode, mSystemUiVisibility, mIsTranslucent);
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index bb0e5ae..2164de9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -38,6 +38,7 @@
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
@@ -67,12 +68,22 @@
             int windowFlags, Rect taskBounds) {
         final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888,
                 GraphicBuffer.USAGE_SW_READ_RARELY | GraphicBuffer.USAGE_SW_WRITE_NEVER);
+
+        // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
+        // this behavior set the taskSize to be the same as the taskBounds width and height. The
+        // taskBounds passed here are assumed to be the same task bounds as when the snapshot was
+        // taken. We assume there is no aspect ratio mismatch between the screenshot and the
+        // taskBounds
+        assertEquals(width, taskBounds.width());
+        assertEquals(height, taskBounds.height());
+        Point taskSize = new Point(taskBounds.width(), taskBounds.height());
+
         final TaskSnapshot snapshot = new TaskSnapshot(
                 System.currentTimeMillis(),
                 new ComponentName("", ""), buffer,
                 ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
-                Surface.ROTATION_0, contentInsets, false,
-                1.0f, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+                Surface.ROTATION_0, taskSize, contentInsets, false,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
                 0 /* systemUiVisibility */, false /* isTranslucent */);
         mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test",
                 createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), sysuiVis, windowFlags, 0,
@@ -152,7 +163,7 @@
 
     @Test
     public void testCalculateSnapshotCrop_taskNotOnTop() {
-        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 100));
+        setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150));
         assertEquals(new Rect(0, 10, 100, 90), mSurface.calculateSnapshotCrop());
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9d77623..6a40487 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4096,7 +4096,8 @@
         /** Prefix of all Wifi.KEY_* constants. */
         public static final String KEY_PREFIX = "wifi.";
         /**
-        * It contains the maximum client count definition that the carrier owns.
+        * It contains the maximum client count definition that the carrier sets.
+        * The default is 0, which means that the carrier hasn't set a requirement.
         */
         public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT =
                 KEY_PREFIX + "hotspot_maximum_client_count";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0660776..a36df49 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2788,39 +2788,22 @@
     /**
      * Returns the ISO-3166 country code equivalent of the MCC (Mobile Country Code) of the current
      * registered operator or the cell nearby, if available.
-     * <p>
-     * The ISO-3166 country code is provided in lowercase 2 character format.
-     * <p>
-     * Note: In multi-sim, this returns a shared emergency network country iso from other
-     * subscription if the subscription used to create the TelephonyManager doesn't camp on
-     * a network due to some reason (e.g. pin/puk locked), or sim is absent in the corresponding
-     * slot.
+     *
      * Note: Result may be unreliable on CDMA networks (use {@link #getPhoneType()} to determine
      * if on a CDMA network).
      * <p>
      * @return the lowercase 2 character ISO-3166 country code, or empty string if not available.
      */
     public String getNetworkCountryIso() {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony == null) return "";
-            return telephony.getNetworkCountryIsoForPhone(getPhoneId(),
-                    null /* no permission check */, null);
-        } catch (RemoteException ex) {
-            return "";
-        }
+        return getNetworkCountryIso(getSlotIndex());
     }
 
     /**
      * Returns the ISO-3166 country code equivalent of the MCC (Mobile Country Code) of the current
-     * registered operator or the cell nearby, if available.
-     * <p>
-     * The ISO-3166 country code is provided in lowercase 2 character format.
-     * <p>
-     * Note: In multi-sim, this returns a shared emergency network country iso from other
-     * subscription if the subscription used to create the TelephonyManager doesn't camp on
-     * a network due to some reason (e.g. pin/puk locked), or sim is absent in the corresponding
-     * slot.
+     * registered operator or the cell nearby, if available. This is same as
+     * {@link #getNetworkCountryIso()} but allowing specifying the SIM slot index. This is used for
+     * accessing network country info from the SIM slot that does not have SIM inserted.
+     *
      * Note: Result may be unreliable on CDMA networks (use {@link #getPhoneType()} to determine
      * if on a CDMA network).
      * <p>
@@ -2831,22 +2814,18 @@
      *
      * @throws IllegalArgumentException when the slotIndex is invalid.
      *
-     * {@hide}
      */
-    @SystemApi
-    @TestApi
     @NonNull
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getNetworkCountryIso(int slotIndex) {
         try {
-            if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
+            if (slotIndex != SubscriptionManager.DEFAULT_SIM_SLOT_INDEX
+                    && !SubscriptionManager.isValidSlotIndex(slotIndex)) {
                 throw new IllegalArgumentException("invalid slot index " + slotIndex);
             }
 
             ITelephony telephony = getITelephony();
             if (telephony == null) return "";
-            return telephony.getNetworkCountryIsoForPhone(slotIndex, getOpPackageName(),
-                    getFeatureId());
+            return telephony.getNetworkCountryIsoForPhone(slotIndex);
         } catch (RemoteException ex) {
             return "";
         }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 861925f..af5089f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -281,7 +281,7 @@
      * operator's MCC (Mobile Country Code).
      * @see android.telephony.TelephonyManager#getNetworkCountryIso
      */
-    String getNetworkCountryIsoForPhone(int phoneId, String callingPkg, String callingFeatureId);
+    String getNetworkCountryIsoForPhone(int phoneId);
 
     /**
      * Returns the neighboring cell information of the device.
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
index 1c8b6be..61f3dba 100644
--- a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -30,6 +30,7 @@
     private ITestDevice mTestDevice;
     private static final String SYSTEM_SERVER_PROFILE =
             "/data/misc/profiles/cur/0/android/primary.prof";
+    private static final boolean USE_PHENOTYPE = false;
 
     @Override
     public void setDevice(ITestDevice testDevice) {
@@ -41,16 +42,33 @@
         return mTestDevice;
     }
 
+    private String getProperty(String property) throws Exception {
+        if (USE_PHENOTYPE) {
+            return mTestDevice.getProperty("persist.device_config.runtime_native_boot."
+                    + property);
+        } else {
+            return mTestDevice.executeShellCommand("getprop dalvik.vm." + property).trim();
+        }
+    }
+
+    private String setProperty(String property, String value) throws Exception {
+        if (USE_PHENOTYPE) {
+            return mTestDevice.executeShellCommand(
+                "device_config put runtime_native_boot " + property + " " + value);
+        } else {
+            return mTestDevice.executeShellCommand(
+                "setprop dalvik.vm." + property + " " + value);
+        }
+    }
+
     /**
      * Validate that the boot image profile properties are set.
      */
     public void validateProperties() throws Exception {
-        String res = mTestDevice.getProperty(
-                "persist.device_config.runtime_native_boot.profilebootclasspath");
-        assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
-        res = mTestDevice.getProperty(
-                "persist.device_config.runtime_native_boot.profilesystemserver");
-        assertTrue("profile system server not enabled", res != null && res.equals("true"));
+        String res = getProperty("profilebootclasspath");
+        assertTrue("profile boot class path not enabled: " + res, "true".equals(res));
+        res = getProperty("profilesystemserver");
+        assertTrue("profile system server not enabled: " + res, "true".equals(res));
     }
 
     private boolean forceSaveProfile(String pkg) throws Exception {
@@ -67,33 +85,48 @@
     @Test
     public void testSystemServerProfile() throws Exception {
         final int numIterations = 20;
+        String res;
+        // Set properties and wait for them to be readable.
         for (int i = 1; i <= numIterations; ++i) {
-            String res;
-            res = mTestDevice.getProperty(
-                    "persist.device_config.runtime_native_boot.profilebootclasspath");
-            boolean profileBootClassPath = res != null && res.equals("true");
-            res = mTestDevice.getProperty(
-                    "persist.device_config.runtime_native_boot.profilesystemserver");
-            boolean profileSystemServer = res != null && res.equals("true");
+            String pbcp = getProperty("profilebootclasspath");
+            boolean profileBootClassPath = "true".equals(pbcp);
+            String pss = getProperty("profilesystemserver");
+            boolean profileSystemServer = "true".equals(pss);
             if (profileBootClassPath && profileSystemServer) {
                 break;
             }
             if (i == numIterations) {
-                assertTrue("profile system server not enabled", profileSystemServer);
-                assertTrue("profile boot class path not enabled", profileSystemServer);
+                assertTrue("profile system server not enabled: " + pss, profileSystemServer);
+                assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath);
             }
 
-            res = mTestDevice.executeShellCommand(
-                    "device_config put runtime_native_boot profilebootclasspath true");
-            res = mTestDevice.executeShellCommand(
-                    "device_config put runtime_native_boot profilesystemserver true");
-            res = mTestDevice.executeShellCommand("stop");
-            res = mTestDevice.executeShellCommand("start");
-            Thread.sleep(5000);
+            setProperty("profilebootclasspath", "true");
+            setProperty("profilesystemserver", "true");
+            Thread.sleep(1000);
         }
+
+        // Restart shell and wait for system boot.
+        res = mTestDevice.executeShellCommand("stop");
+        assertTrue("stop shell: " + res, res.length() == 0);
+        res = mTestDevice.executeShellCommand("start");
+        assertTrue("start shell: " + res, res.length() == 0);
+        for (int i = 1; i <= numIterations; ++i) {
+            String pbcp = getProperty("profilebootclasspath");
+            boolean profileBootClassPath = "true".equals(pbcp);
+            String pss = getProperty("profilesystemserver");
+            boolean profileSystemServer = "true".equals(pss);
+            if (profileBootClassPath && profileSystemServer) {
+                break;
+            }
+            if (i == numIterations) {
+                assertTrue("profile system server not enabled: " + pss, profileSystemServer);
+                assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath);
+            }
+            Thread.sleep(1000);
+        }
+
         // Trunacte the profile before force it to be saved to prevent previous profiles
         // causing the test to pass.
-        String res;
         res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim();
         assertTrue(res, res.length() == 0);
         // Wait up to 20 seconds for the profile to be saved.