Merge "Add TestAPI to replace content on a display." into main
diff --git a/Android.bp b/Android.bp
index 93d6f53..be589b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -246,7 +246,6 @@
         "android.system.suspend.control.internal-java",
         "devicepolicyprotosnano",
 
-        "com.android.sysprop.apex",
         "com.android.sysprop.init",
         "com.android.sysprop.localization",
         "PlatformProperties",
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index fb342b9..913a76a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -61,6 +61,7 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
@@ -802,6 +803,9 @@
                     }
                 }
             }
+        } catch (FileNotFoundException e) {
+            // Expected on first boot
+            Slog.d(TAG, "App idle file for user " + userId + " does not exist");
         } catch (IOException | XmlPullParserException e) {
             Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
         } finally {
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 4089cfe..653e243 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -15,8 +15,6 @@
  */
 package android.content.res;
 
-import static android.content.res.Resources.ID_NULL;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -388,7 +386,7 @@
         synchronized (this) {
             long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
             try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
-                XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
+                XmlResourceParser parser = block.newParser();
                 // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
                 // which makes newParser always return non-null. But let's be careful.
                 if (parser == null) {
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index a6fea6a..62a46b6 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -17,7 +17,6 @@
 package android.content.res;
 
 import android.annotation.NonNull;
-import android.util.ArrayMap;
 import android.util.Pools.SimplePool;
 
 import androidx.annotation.StyleableRes;
@@ -27,9 +26,6 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.util.Iterator;
-import java.util.Map;
-
 /**
  * Defines the string attribute length and child tag count restrictions for a xml element.
  *
@@ -161,9 +157,11 @@
     private static final String[] NAME_VALUE_ATTRS = {TAG_ATTR_NAME, TAG_ATTR_VALUE};
 
     private String[] mStringAttrNames = new String[0];
-    private final Map<String, TagCounter> mTagCounters = new ArrayMap<>();
+    // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
+    // tags are added then the size here should be increased to match.
+    private final TagCounter[] mTagCounters = new TagCounter[35];
 
-    private String mTag;
+    String mTag;
 
     private static final ThreadLocal<SimplePool<Element>> sPool =
             ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE));
@@ -180,17 +178,91 @@
 
     void recycle() {
         mStringAttrNames = new String[0];
-        Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator();
-        while (it.hasNext()) {
-            it.next().getValue().recycle();
-            it.remove();
-        }
         mTag = null;
         sPool.get().release(this);
     }
 
+    private long mChildTagMask = 0;
+
+    private static int getCounterIdx(String tag) {
+        switch(tag) {
+            case TAG_LAYOUT:
+                return 0;
+            case TAG_META_DATA:
+                return 1;
+            case TAG_INTENT_FILTER:
+                return 2;
+            case TAG_PROFILEABLE:
+                return 3;
+            case TAG_USES_NATIVE_LIBRARY:
+                return 4;
+            case TAG_RECEIVER:
+                return 5;
+            case TAG_SERVICE:
+                return 6;
+            case TAG_ACTIVITY_ALIAS:
+                return 7;
+            case TAG_USES_LIBRARY:
+                return 8;
+            case TAG_PROVIDER:
+                return 9;
+            case TAG_ACTIVITY:
+                return 10;
+            case TAG_ACTION:
+                return 11;
+            case TAG_CATEGORY:
+                return 12;
+            case TAG_DATA:
+                return 13;
+            case TAG_APPLICATION:
+                return 14;
+            case TAG_OVERLAY:
+                return 15;
+            case TAG_INSTRUMENTATION:
+                return 16;
+            case TAG_PERMISSION_GROUP:
+                return 17;
+            case TAG_PERMISSION_TREE:
+                return 18;
+            case TAG_SUPPORTS_GL_TEXTURE:
+                return 19;
+            case TAG_SUPPORTS_SCREENS:
+                return 20;
+            case TAG_USES_CONFIGURATION:
+                return 21;
+            case TAG_USES_PERMISSION_SDK_23:
+                return 22;
+            case TAG_USES_SDK:
+                return 23;
+            case TAG_COMPATIBLE_SCREENS:
+                return 24;
+            case TAG_QUERIES:
+                return 25;
+            case TAG_ATTRIBUTION:
+                return 26;
+            case TAG_USES_FEATURE:
+                return 27;
+            case TAG_PERMISSION:
+                return 28;
+            case TAG_USES_PERMISSION:
+                return 29;
+            case TAG_GRANT_URI_PERMISSION:
+                return 30;
+            case TAG_PATH_PERMISSION:
+                return 31;
+            case TAG_PACKAGE:
+                return 32;
+            case TAG_INTENT:
+                return 33;
+            default:
+                // The size of the mTagCounters array should be equal to this value+1
+                return 34;
+        }
+    }
+
     private void init(String tag) {
         this.mTag = tag;
+        mChildTagMask = 0;
         switch (tag) {
             case TAG_ACTION:
             case TAG_CATEGORY:
@@ -208,29 +280,29 @@
                 break;
             case TAG_ACTIVITY:
                 setStringAttrNames(ACTIVITY_STR_ATTR_NAMES);
-                addTagCounter(1000, TAG_LAYOUT);
-                addTagCounter(8000, TAG_META_DATA);
-                addTagCounter(20000, TAG_INTENT_FILTER);
+                initializeCounter(TAG_LAYOUT, 1000);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_ACTIVITY_ALIAS:
                 setStringAttrNames(ACTIVITY_ALIAS_STR_ATTR_NAMES);
-                addTagCounter(8000, TAG_META_DATA);
-                addTagCounter(20000, TAG_INTENT_FILTER);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_APPLICATION:
                 setStringAttrNames(APPLICATION_STR_ATTR_NAMES);
-                addTagCounter(100, TAG_PROFILEABLE);
-                addTagCounter(100, TAG_USES_NATIVE_LIBRARY);
-                addTagCounter(1000, TAG_RECEIVER);
-                addTagCounter(1000, TAG_SERVICE);
-                addTagCounter(4000, TAG_ACTIVITY_ALIAS);
-                addTagCounter(4000, TAG_USES_LIBRARY);
-                addTagCounter(8000, TAG_PROVIDER);
-                addTagCounter(8000, TAG_META_DATA);
-                addTagCounter(40000, TAG_ACTIVITY);
+                initializeCounter(TAG_PROFILEABLE, 100);
+                initializeCounter(TAG_USES_NATIVE_LIBRARY, 100);
+                initializeCounter(TAG_RECEIVER, 1000);
+                initializeCounter(TAG_SERVICE, 1000);
+                initializeCounter(TAG_ACTIVITY_ALIAS, 4000);
+                initializeCounter(TAG_USES_LIBRARY, 4000);
+                initializeCounter(TAG_PROVIDER, 8000);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_ACTIVITY, 40000);
                 break;
             case TAG_COMPATIBLE_SCREENS:
-                addTagCounter(4000, TAG_SCREEN);
+                initializeCounter(TAG_SCREEN, 4000);
                 break;
             case TAG_DATA:
                 setStringAttrNames(DATA_STR_ATTR_NAMES);
@@ -243,28 +315,28 @@
                 break;
             case TAG_INTENT:
             case TAG_INTENT_FILTER:
-                addTagCounter(20000, TAG_ACTION);
-                addTagCounter(40000, TAG_CATEGORY);
-                addTagCounter(40000, TAG_DATA);
+                initializeCounter(TAG_ACTION, 20000);
+                initializeCounter(TAG_CATEGORY, 40000);
+                initializeCounter(TAG_DATA, 40000);
                 break;
             case TAG_MANIFEST:
                 setStringAttrNames(MANIFEST_STR_ATTR_NAMES);
-                addTagCounter(100, TAG_APPLICATION);
-                addTagCounter(100, TAG_OVERLAY);
-                addTagCounter(100, TAG_INSTRUMENTATION);
-                addTagCounter(100, TAG_PERMISSION_GROUP);
-                addTagCounter(100, TAG_PERMISSION_TREE);
-                addTagCounter(100, TAG_SUPPORTS_GL_TEXTURE);
-                addTagCounter(100, TAG_SUPPORTS_SCREENS);
-                addTagCounter(100, TAG_USES_CONFIGURATION);
-                addTagCounter(100, TAG_USES_PERMISSION_SDK_23);
-                addTagCounter(100, TAG_USES_SDK);
-                addTagCounter(200, TAG_COMPATIBLE_SCREENS);
-                addTagCounter(200, TAG_QUERIES);
-                addTagCounter(400, TAG_ATTRIBUTION);
-                addTagCounter(400, TAG_USES_FEATURE);
-                addTagCounter(2000, TAG_PERMISSION);
-                addTagCounter(20000, TAG_USES_PERMISSION);
+                initializeCounter(TAG_APPLICATION, 100);
+                initializeCounter(TAG_OVERLAY, 100);
+                initializeCounter(TAG_INSTRUMENTATION, 100);
+                initializeCounter(TAG_PERMISSION_GROUP, 100);
+                initializeCounter(TAG_PERMISSION_TREE, 100);
+                initializeCounter(TAG_SUPPORTS_GL_TEXTURE, 100);
+                initializeCounter(TAG_SUPPORTS_SCREENS, 100);
+                initializeCounter(TAG_USES_CONFIGURATION, 100);
+                initializeCounter(TAG_USES_PERMISSION_SDK_23, 100);
+                initializeCounter(TAG_USES_SDK, 100);
+                initializeCounter(TAG_COMPATIBLE_SCREENS, 200);
+                initializeCounter(TAG_QUERIES, 200);
+                initializeCounter(TAG_ATTRIBUTION, 400);
+                initializeCounter(TAG_USES_FEATURE, 400);
+                initializeCounter(TAG_PERMISSION, 2000);
+                initializeCounter(TAG_USES_PERMISSION, 20000);
                 break;
             case TAG_META_DATA:
             case TAG_PROPERTY:
@@ -281,21 +353,21 @@
                 break;
             case TAG_PROVIDER:
                 setStringAttrNames(PROVIDER_STR_ATTR_NAMES);
-                addTagCounter(100, TAG_GRANT_URI_PERMISSION);
-                addTagCounter(100, TAG_PATH_PERMISSION);
-                addTagCounter(8000, TAG_META_DATA);
-                addTagCounter(20000, TAG_INTENT_FILTER);
+                initializeCounter(TAG_GRANT_URI_PERMISSION, 100);
+                initializeCounter(TAG_PATH_PERMISSION, 100);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
             case TAG_QUERIES:
-                addTagCounter(1000, TAG_PACKAGE);
-                addTagCounter(2000, TAG_INTENT);
-                addTagCounter(8000, TAG_PROVIDER);
+                initializeCounter(TAG_PACKAGE, 1000);
+                initializeCounter(TAG_INTENT, 2000);
+                initializeCounter(TAG_PROVIDER, 8000);
                 break;
             case TAG_RECEIVER:
             case TAG_SERVICE:
                 setStringAttrNames(RECEIVER_SERVICE_STR_ATTR_NAMES);
-                addTagCounter(8000, TAG_META_DATA);
-                addTagCounter(20000, TAG_INTENT_FILTER);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
                 break;
         }
     }
@@ -365,12 +437,17 @@
         }
     }
 
-    private void addTagCounter(int max, String tag) {
-        mTagCounters.put(tag, TagCounter.obtain(max));
+    private void initializeCounter(String tag, int max) {
+        int idx = getCounterIdx(tag);
+        if (mTagCounters[idx] == null) {
+            mTagCounters[idx] = new TagCounter();
+        }
+        mTagCounters[idx].reset(max);
+        mChildTagMask |= 1 << idx;
     }
 
     boolean hasChild(String tag) {
-        return mTagCounters.containsKey(tag);
+        return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
     }
 
     void validateStringAttrs(@NonNull XmlPullParser attrs) throws XmlPullParserException {
@@ -393,8 +470,8 @@
     }
 
     void seen(@NonNull Element element) throws XmlPullParserException {
-        if (mTagCounters.containsKey(element.mTag)) {
-            TagCounter counter = mTagCounters.get(element.mTag);
+        TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
+        if (counter != null) {
             counter.increment();
             if (!counter.isValid()) {
                 throw new XmlPullParserException("The number of child " + element.mTag
diff --git a/core/java/android/content/res/TagCounter.java b/core/java/android/content/res/TagCounter.java
index 0e5510f..94deee7 100644
--- a/core/java/android/content/res/TagCounter.java
+++ b/core/java/android/content/res/TagCounter.java
@@ -16,47 +16,25 @@
 
 package android.content.res;
 
-import android.annotation.NonNull;
-import android.util.Pools.SimplePool;
-
 /**
  * Counter used to track the number of tags seen during manifest validation.
  *
  * {@hide}
  */
 public class TagCounter {
-    private static final int MAX_POOL_SIZE = 512;
     private static final int DEFAULT_MAX_COUNT = 512;
 
-    private static final ThreadLocal<SimplePool<TagCounter>> sPool =
-            ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE));
-
     private int mMaxValue;
     private int mCount;
 
-    @NonNull
-    static TagCounter obtain(int max) {
-        TagCounter counter = sPool.get().acquire();
-        if (counter == null) {
-            counter = new TagCounter();
-        }
-        counter.setMaxValue(max);
-        return counter;
-    }
-
-    void recycle() {
-        mCount = 0;
-        mMaxValue = DEFAULT_MAX_COUNT;
-        sPool.get().release(this);
-    }
-
     public TagCounter() {
         mMaxValue = DEFAULT_MAX_COUNT;
         mCount = 0;
     }
 
-    private void setMaxValue(int maxValue) {
+    void reset(int maxValue) {
         this.mMaxValue = maxValue;
+        this.mCount = 0;
     }
 
     void increment() {
@@ -66,8 +44,4 @@
     public boolean isValid() {
         return mCount <= mMaxValue;
     }
-
-    int value() {
-        return mCount;
-    }
 }
diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java
index daa7dfe..8b5e6c6 100644
--- a/core/java/android/content/res/Validator.java
+++ b/core/java/android/content/res/Validator.java
@@ -24,9 +24,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.ArrayDeque;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
 
 /**
  * Validates manifest files by ensuring that tag counts and the length of string attributes are
@@ -36,30 +33,12 @@
  */
 public class Validator {
 
-    private static final int MAX_TAG_COUNT = 100000;
-
     private final ArrayDeque<Element> mElements = new ArrayDeque<>();
-    private final Map<String, TagCounter> mTagCounters = new HashMap<>();
 
     private void cleanUp() {
         while (!mElements.isEmpty()) {
             mElements.pop().recycle();
         }
-        Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator();
-        while (it.hasNext()) {
-            it.next().getValue().recycle();
-            it.remove();
-        }
-    }
-
-    private void seen(String tag) throws XmlPullParserException {
-        mTagCounters.putIfAbsent(tag, TagCounter.obtain(MAX_TAG_COUNT));
-        TagCounter counter = mTagCounters.get(tag);
-        counter.increment();
-        if (!counter.isValid()) {
-            throw new XmlPullParserException("The number of " + tag
-                    + " tags exceeded " + MAX_TAG_COUNT);
-        }
     }
 
     /**
@@ -84,7 +63,6 @@
                 }
                 Element parent = mElements.peek();
                 if (parent == null || parent.hasChild(tag)) {
-                    seen(tag);
                     Element element = Element.obtain(tag);
                     element.validateStringAttrs(parser);
                     if (parent != null) {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index aa67693..0461b2e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -547,6 +547,30 @@
     }
 
     /**
+     * Resolve default values into integer amplitude numbers.
+     *
+     * @param defaultAmplitude the default amplitude to apply, must be between 0 and
+     *                         MAX_AMPLITUDE
+     * @return this if amplitude value is already set, or a copy of this effect with given default
+     *         amplitude otherwise
+     *
+     * @hide
+     */
+    public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
+
+    /**
+     * Scale the vibration effect intensity with the given constraints.
+     *
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up
+     * @return this if there is no scaling to be done, or a copy of this effect with scaled
+     *         vibration intensity otherwise
+     *
+     * @hide
+     */
+    public abstract <T extends VibrationEffect> T scale(float scaleFactor);
+
+    /**
      * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
      * should only be applied once to an original effect - it shouldn't be applied to the
      * result of this method.
@@ -822,6 +846,40 @@
         /** @hide */
         @NonNull
         @Override
+        public Composed resolve(int defaultAmplitude) {
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+            }
+            if (resolvedSegments.equals(mSegments)) {
+                return this;
+            }
+            Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+            resolved.validate();
+            return resolved;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
+        public Composed scale(float scaleFactor) {
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+            }
+            if (scaledSegments.equals(mSegments)) {
+                return this;
+            }
+            Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+            scaled.validate();
+            return scaled;
+        }
+
+        /** @hide */
+        @NonNull
+        @Override
         public Composed applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) {
             boolean isRepeating = mRepeatIndex >= 0;
             if (isRepeating == wantRepeating) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9f1cc71..a626775 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14830,7 +14830,8 @@
      * view satisfies any of the following:
      * <ul>
      * <li>Is actionable, e.g. {@link #isClickable()},
-     * {@link #isLongClickable()}, or {@link #isFocusable()}
+     * {@link #isLongClickable()}, {@link #isContextClickable()},
+     * {@link #isScreenReaderFocusable()}, or {@link #isFocusable()}
      * <li>Has an {@link AccessibilityDelegate}
      * <li>Has an interaction listener, e.g. {@link OnTouchListener},
      * {@link OnKeyListener}, etc.
@@ -14839,6 +14840,7 @@
      * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
      * </ul>
      * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
+     * <li>Is an accessibility heading, see {@link #setAccessibilityHeading(boolean)}.</li>
      * </ol>
      *
      * @return Whether the view is exposed for accessibility.
@@ -14865,7 +14867,7 @@
         return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
                 || hasListenersForAccessibility() || mAccessibilityDelegate != null
                 || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
-                || isAccessibilityPane();
+                || isAccessibilityPane() || isAccessibilityHeading();
     }
 
     /**
@@ -15025,7 +15027,8 @@
      * @hide
      */
     public boolean isActionableForAccessibility() {
-        return (isClickable() || isLongClickable() || isFocusable());
+        return (isClickable() || isLongClickable() || isFocusable() || isContextClickable()
+                || isScreenReaderFocusable());
     }
 
     /**
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 9107236..73954da 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -38,6 +38,8 @@
 import android.hardware.vibrator.IVibrator;
 import android.net.Uri;
 import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -634,6 +636,88 @@
                         .validate());
     }
 
+    @Test
+    public void testResolveOneShot() {
+        VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+
+        assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
+    }
+
+    @Test
+    public void testResolveWaveform() {
+        VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+        assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
+
+        assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
+    }
+
+    @Test
+    public void testResolvePrebaked() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        assertEquals(effect, effect.resolve(51));
+    }
+
+    @Test
+    public void testResolveComposed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
+                .compose();
+        assertEquals(effect, effect.resolve(51));
+
+        VibrationEffect.Composed resolved = VibrationEffect.startComposition()
+                .addEffect(DEFAULT_ONE_SHOT)
+                .compose()
+                .resolve(51);
+        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
+    }
+
+    @Test
+    public void testScaleOneShot() {
+        VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+        VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+    }
+
+    @Test
+    public void testScaleWaveform() {
+        VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+        assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+        VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+        assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+    }
+
+    @Test
+    public void testScalePrebaked() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+        assertEquals(effect, scaledUp);
+
+        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+        assertEquals(effect, scaledDown);
+    }
+
+    @Test
+    public void testScaleComposed() {
+        VibrationEffect.Composed effect =
+                (VibrationEffect.Composed) VibrationEffect.startComposition()
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+                    .addEffect(TEST_ONE_SHOT)
+                    .compose();
+
+        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+        assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
+        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude());
+
+        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+        assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
+        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
+    }
+
     private void doTestApplyRepeatingWithNonRepeatingOriginal(@NotNull VibrationEffect original) {
         assertTrue(original.getDuration() != Long.MAX_VALUE);
         int loopDelayMs = 123;
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index d7ac501..dbd9ef3 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -79,7 +79,7 @@
     }
 
     // flush will create a GrRenderTarget if not already present.
-    canvas->flush();
+    directContext->flushAndSubmit();
 
     GLuint fboID = 0;
     SkISize fboSize;
@@ -167,7 +167,7 @@
 
         // GL ops get inserted here if previous flush is missing, which could dirty the stencil
         bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
-        tmpCanvas->flush();  // need this flush for the single op that draws into the stencil
+        directContext->flushAndSubmit();  // need this flush for the single op that draws into the stencil
 
         // ensure that the framebuffer that the webview will render into is bound before after we
         // draw into the stencil
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 447cd7b..fe51ed5 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -321,4 +321,7 @@
 
     <!-- Whether vibrate icon is shown in the status bar by default. -->
     <integer name="def_statusBarVibrateIconEnabled">0</integer>
+
+    <!-- Whether predictive back animation is enabled by default. -->
+    <bool name="def_enable_back_animation">false</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7a60507..d1d745f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -246,6 +246,7 @@
     public static final String RESULT_ROWS_DELETED = "result_rows_deleted";
     public static final String RESULT_SETTINGS_LIST = "result_settings_list";
 
+    public static final String SETTINGS_PROVIDER_JOBS_NS = "SettingsProviderJobsNamespace";
     // Used for scheduling jobs to make a copy for the settings files
     public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1;
     public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L;
@@ -2785,12 +2786,13 @@
      */
     public void scheduleWriteFallbackFilesJob() {
         final Context context = getContext();
-        final JobScheduler jobScheduler =
+        JobScheduler jobScheduler =
                 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         if (jobScheduler == null) {
             // Might happen: SettingsProvider is created before JobSchedulerService in system server
             return;
         }
+        jobScheduler = jobScheduler.forNamespace(SETTINGS_PROVIDER_JOBS_NS);
         // Check if the job is already scheduled. If so, skip scheduling another one
         if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) {
             return;
@@ -3773,7 +3775,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 220;
+            private static final int SETTINGS_VERSION = 221;
 
             private final int mUserId;
 
@@ -5867,6 +5869,21 @@
                     currentVersion = 220;
                 }
 
+                if (currentVersion == 220) {
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting enableBackAnimation =
+                            globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION);
+                    if (enableBackAnimation.isNull()) {
+                        final boolean defEnableBackAnimation =
+                                getContext()
+                                        .getResources()
+                                        .getBoolean(R.bool.def_enable_back_animation);
+                        initGlobalSettingsDefaultValLocked(
+                                Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation);
+                    }
+                    currentVersion = 221;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
index 66aa7ba..91e8bf8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.settings;
 
+import static com.android.providers.settings.SettingsProvider.SETTINGS_PROVIDER_JOBS_NS;
 import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG;
 import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL;
 import static com.android.providers.settings.SettingsProvider.TABLE_SECURE;
@@ -35,7 +36,8 @@
 public class WriteFallbackSettingsFilesJobService extends JobService {
     @Override
     public boolean onStartJob(final JobParameters params) {
-        if (params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
+        if (!SETTINGS_PROVIDER_JOBS_NS.equals(params.getJobNamespace())
+                || params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
             return false;
         }
         final List<String> settingsFiles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f68bd49..35cf4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -106,6 +107,7 @@
         StatusBarEventsModule.class,
         StartCentralSurfacesModule.class,
         VolumeModule.class,
+        WallpaperModule.class,
         KeyboardShortcutsModule.class
 })
 public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index b1f513d..a560acc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
 import com.android.systemui.statusbar.phone.LockscreenWallpaper
+import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -59,6 +60,7 @@
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
 import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wallpapers.dagger.WallpaperModule
 import com.android.systemui.wmshell.WMShell
 import dagger.Binds
 import dagger.Module
@@ -72,6 +74,7 @@
     MultiUserUtilsModule::class,
     StartControlsStartableModule::class,
     StartBinderLoggerModule::class,
+    WallpaperModule::class,
 ])
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
@@ -316,4 +319,9 @@
     @IntoMap
     @ClassKey(LockscreenWallpaper::class)
     abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(ScrimController::class)
+    abstract fun bindScrimController(impl: ScrimController): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 278ae95..72915a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3565,6 +3565,10 @@
         }
     };
 
+    /**
+     * @deprecated See {@link com.android.systemui.wallpapers.data.repository.WallpaperRepository}
+     * instead.
+     */
     private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -3584,7 +3588,6 @@
                     && (info != null && info.supportsAmbientMode());
 
             mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-            mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
             mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e4e912e..33bf06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,6 +48,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -71,8 +72,10 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -89,7 +92,8 @@
  * security method gets shown).
  */
 @SysUISingleton
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable,
+        CoreStartable {
 
     static final String TAG = "ScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -207,6 +211,7 @@
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
     private final Executor mMainExecutor;
+    private final JavaAdapter mJavaAdapter;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -268,6 +273,7 @@
     private boolean mKeyguardOccluded;
 
     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final WallpaperRepository mWallpaperRepository;
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@@ -297,11 +303,13 @@
             DockManager dockManager,
             ConfigurationController configurationController,
             @Main Executor mainExecutor,
+            JavaAdapter javaAdapter,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
             FeatureFlags featureFlags) {
@@ -317,6 +325,7 @@
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
+        mJavaAdapter = javaAdapter;
         mScreenOffAnimationController = screenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
@@ -348,9 +357,17 @@
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
     }
 
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+                this::setWallpaperSupportsAmbientMode);
+    }
+
     /**
      * Attach the controller to the supplied views.
      */
@@ -1551,7 +1568,7 @@
         pw.println(mState.getMaxLightRevealScrimAlpha());
     }
 
-    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+    private void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
         ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 5e489b0..82589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
 
 import dagger.Subcomponent;
 
@@ -39,6 +40,7 @@
         DefaultComponentBinder.class,
         DependencyProvider.class,
         KeyguardModule.class,
+        NoopWallpaperModule.class,
         NotificationRowModule.class,
         NotificationsModule.class,
         RecentsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
new file mode 100644
index 0000000..baf88b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.NoopWallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface NoopWallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: NoopWallpaperRepository): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
new file mode 100644
index 0000000..1b89978
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface WallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: WallpaperRepositoryImpl): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
new file mode 100644
index 0000000..a640589
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A no-op implementation of [WallpaperRepository].
+ *
+ * Used for variants of SysUI that do not support wallpaper but require other SysUI classes that
+ * have a wallpaper dependency.
+ */
+@SysUISingleton
+class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
new file mode 100644
index 0000000..48895ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository storing information about the current wallpaper. */
+interface WallpaperRepository {
+    /** Emits true if the current user's current wallpaper supports ambient mode. */
+    val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class WallpaperRepositoryImpl
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    broadcastDispatcher: BroadcastDispatcher,
+    userRepository: UserRepository,
+    private val wallpaperManager: WallpaperManager,
+    context: Context,
+) : WallpaperRepository {
+    private val deviceSupportsAodWallpaper =
+        context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper)
+
+    private val wallpaperChanged: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
+                user = UserHandle.ALL,
+            )
+            // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
+            // input flows emit at least once. Since this flow is an input flow, it needs to emit
+            // when it starts up to ensure that the `combine` will run if the user changes before we
+            // receive a ACTION_WALLPAPER_CHANGED intent.
+            // Note that the `selectedUser` flow does *not* need to emit on start because
+            // [UserRepository.selectedUser] is a state flow which will automatically emit a value
+            // on start.
+            .onStart { emit(Unit) }
+
+    private val selectedUser: Flow<SelectedUserModel> =
+        userRepository.selectedUser
+            // Only update the wallpaper status once the user selection has finished.
+            .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+
+    override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+        if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
+            MutableStateFlow(false).asStateFlow()
+        } else {
+            combine(wallpaperChanged, selectedUser) { _, selectedUser ->
+                    doesWallpaperSupportAmbientMode(selectedUser)
+                }
+                .stateIn(
+                    scope,
+                    // Always be listening for wallpaper changes.
+                    SharingStarted.Eagerly,
+                    initialValue =
+                        doesWallpaperSupportAmbientMode(userRepository.selectedUser.value),
+                )
+        }
+
+    private fun doesWallpaperSupportAmbientMode(selectedUser: SelectedUserModel): Boolean {
+        return wallpaperManager
+            .getWallpaperInfoForUser(
+                selectedUser.userInfo.id,
+            )
+            // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+            ?.supportsAmbientMode() == true
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index bbc75c9..0244e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -59,6 +59,7 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ShadeInterpolation;
@@ -77,9 +78,11 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.utils.os.FakeHandler;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
 
 import com.google.common.truth.Expect;
 
@@ -100,6 +103,7 @@
 import java.util.Map;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -113,6 +117,9 @@
     private final LargeScreenShadeInterpolator
             mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
 
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
     private ScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mNotificationsScrim;
@@ -136,6 +143,7 @@
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
     @Mock private TypedArray mMockTypedArray;
 
@@ -274,20 +282,26 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
                 mLinearLargeScreenShadeInterpolator,
                 mFeatureFlags);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
 
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
     }
@@ -388,7 +402,9 @@
 
     @Test
     public void transitionToAod_withAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -410,7 +426,9 @@
     @Test
     public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
         mScrimController.setHasBackdrop(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -427,7 +445,9 @@
 
     @Test
     public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
@@ -540,7 +560,9 @@
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
         // the back scrim opacity - otherwise it would hide AoD wallpapers.
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
@@ -968,19 +990,23 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
                 mLinearLargeScreenShadeInterpolator,
                 mFeatureFlags);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
 
@@ -1105,7 +1131,9 @@
 
     @Test
     public void testWillHideAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1116,7 +1144,8 @@
     public void testWillHideDockedWallpaper() {
         mAlwaysOnEnabled = false;
         when(mDockManager.isDocked()).thenReturn(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
 
         mScrimController.transitionTo(ScrimState.AOD);
 
@@ -1165,7 +1194,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
@@ -1182,7 +1213,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
new file mode 100644
index 0000000..6fc36b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of the wallpaper repository. */
+class FakeWallpaperRepository : WallpaperRepository {
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
new file mode 100644
index 0000000..132b9b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperRepositoryImplTest : SysuiTestCase() {
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+    private val wallpaperManager: WallpaperManager = mock()
+
+    private val underTest: WallpaperRepositoryImpl by lazy {
+        WallpaperRepositoryImpl(
+            testScope.backgroundScope,
+            fakeBroadcastDispatcher,
+            userRepository,
+            wallpaperManager,
+            context,
+        )
+    }
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+            true,
+        )
+    }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_nullInfo_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoSupports_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnUserChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user is switched to a user with unsupported wallpaper
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // THEN it's false
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user has started switching to a user with unsupported wallpaper but hasn't
+            // finished yet
+            userRepository.selectedUser.value =
+                SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // THEN it still matches the old user
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            val info: WallpaperInfo = mock()
+            whenever(info.supportsAmbientMode()).thenReturn(false)
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(info)
+
+            assertThat(latest).isFalse()
+
+            // WHEN the info now supports ambient mode and a broadcast is sent
+            whenever(info.supportsAmbientMode()).thenReturn(true)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the flow updates
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() =
+        testScope.runTest {
+            whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because wallpaper isn't supported
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+                false
+            )
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because the device doesn't support it
+            assertThat(latest).isFalse()
+        }
+
+    private companion object {
+        val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+        val UNSUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(false) }
+
+        val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+        val SUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+    }
+}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index bca2d60..caf1684 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -35,7 +35,6 @@
 import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
-import android.sysprop.ApexProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1208,8 +1207,7 @@
                             boolean systemExt = permFile.toPath().startsWith(
                                     Environment.getSystemExtDirectory().toPath() + "/");
                             boolean apex = permFile.toPath().startsWith(
-                                    Environment.getApexDirectory().toPath() + "/")
-                                    && ApexProperties.updatable().orElse(false);
+                                    Environment.getApexDirectory().toPath() + "/");
                             if (vendor) {
                                 readPrivAppPermissions(parser,
                                         mPermissionAllowlist.getVendorPrivilegedAppAllowlist());
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f4c8a57..146215b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -357,15 +357,25 @@
             @Override
             public void onPackageAdded(String packageName, int uid) {
                 // Called on a handler, and running as the system
-                UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
-                cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                try {
+                    UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+                    cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
+                }
             }
 
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
                 // Called on a handler, and running as the system
-                UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
-                cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                try {
+                    UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+                    cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
+                }
             }
         }.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
 
@@ -391,6 +401,9 @@
                     }
                 } catch (NameNotFoundException e) {
                     /* ignore */
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
                 }
             }
         });
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1dff62e..b6e58ad 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2124,11 +2124,7 @@
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot setMode: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot setMode", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "setMode");
             return;
         }
 
@@ -2580,11 +2576,7 @@
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot checkOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot checkOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "checkOperation");
             return AppOpsManager.opToDefaultMode(code);
         }
 
@@ -2796,11 +2788,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot noteOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot noteOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "noteOperation");
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3333,11 +3321,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot startOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot startOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "startOperation");
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3513,11 +3497,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot finishOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot finishOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "finishOperation");
             return;
         }
 
@@ -4073,6 +4053,17 @@
         return false;
     }
 
+    private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
+            @NonNull String methodName) {
+        if (Process.isIsolated(uid)) {
+            Slog.e(TAG, "Cannot " + methodName + ": isolated UID");
+        } else if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+            Slog.e(TAG, "Cannot " + methodName + ": non-application UID " + uid);
+        } else {
+            Slog.e(TAG, "Cannot " + methodName, e);
+        }
+    }
+
     /**
      * Get (and potentially create) ops.
      *
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 9069eb2..38c6a52 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -193,8 +193,11 @@
         if (IS_EMULATOR) {
             mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                 synchronized (mLock) {
-                    setPrimaryClipInternalLocked(getClipboardLocked(0, DEVICE_ID_DEFAULT), clip,
-                            android.os.Process.SYSTEM_UID, null);
+                    Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
+                    if (clipboard != null) {
+                        setPrimaryClipInternalLocked(clipboard, clip, android.os.Process.SYSTEM_UID,
+                                null);
+                    }
                 }
             });
         } else {
@@ -637,6 +640,9 @@
                 }
 
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                if (clipboard == null) {
+                    return null;
+                }
                 showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
                 if (clipboard.primaryClip != null) {
@@ -665,7 +671,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                return clipboard.primaryClip != null
+                return (clipboard != null && clipboard.primaryClip != null)
                         ? clipboard.primaryClip.getDescription() : null;
             }
         }
@@ -688,7 +694,8 @@
                 return false;
             }
             synchronized (mLock) {
-                return getClipboardLocked(intendingUserId, intendingDeviceId).primaryClip != null;
+                Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                return clipboard != null && clipboard.primaryClip != null;
             }
         }
 
@@ -709,8 +716,11 @@
                 return;
             }
             synchronized (mLock) {
-                getClipboardLocked(intendingUserId, intendingDeviceId)
-                        .primaryClipListeners
+                Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                if (clipboard == null) {
+                    return;
+                }
+                clipboard.primaryClipListeners
                         .register(
                                 listener,
                                 new ListenerInfo(intendingUid, callingPackage, attributionTag));
@@ -733,8 +743,11 @@
                 return;
             }
             synchronized (mLock) {
-                getClipboardLocked(intendingUserId,
-                        intendingDeviceId).primaryClipListeners.unregister(listener);
+                Clipboard clipboard = getClipboardLocked(intendingUserId,
+                                intendingDeviceId);
+                if (clipboard != null) {
+                    clipboard.primaryClipListeners.unregister(listener);
+                }
             }
         }
 
@@ -757,7 +770,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                if (clipboard.primaryClip != null) {
+                if (clipboard != null && clipboard.primaryClip != null) {
                     CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
                     return text != null && text.length() > 0;
                 }
@@ -786,7 +799,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                if (clipboard.primaryClip != null) {
+                if (clipboard != null && clipboard.primaryClip != null) {
                     return clipboard.mPrimaryClipPackage;
                 }
                 return null;
@@ -808,7 +821,8 @@
                         final int intendingUid = msg.arg2;
                         final int intendingDeviceId = ((Pair<Integer, Integer>) msg.obj).second;
                         synchronized (mLock) {
-                            if (getClipboardLocked(userId, intendingDeviceId).primaryClip != null) {
+                            Clipboard clipboard = getClipboardLocked(userId, intendingDeviceId);
+                            if (clipboard != null && clipboard.primaryClip != null) {
                                 FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
                                         FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
                                 setPrimaryClipInternalLocked(
@@ -824,9 +838,23 @@
     };
 
     @GuardedBy("mLock")
-    private Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
+    private @Nullable Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
         Clipboard clipboard = mClipboards.get(userId, deviceId);
         if (clipboard == null) {
+            try {
+                if (!mUm.isUserRunning(userId)) {
+                    Slog.w(TAG, "getClipboardLocked called with not running userId " + userId);
+                    return null;
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException calling UserManager: " + e);
+                return null;
+            }
+            if (deviceId != DEVICE_ID_DEFAULT && !mVdm.isValidVirtualDeviceId(deviceId)) {
+                Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId "
+                        + deviceId);
+                return null;
+            }
             clipboard = new Clipboard(userId, deviceId);
             mClipboards.add(userId, deviceId, clipboard);
         }
@@ -876,8 +904,11 @@
         final int userId = UserHandle.getUserId(uid);
 
         // Update this user
-        setPrimaryClipInternalLocked(getClipboardLocked(userId, deviceId), clip, uid,
-                sourcePackage);
+        Clipboard clipboard = getClipboardLocked(userId, deviceId);
+        if (clipboard == null) {
+            return;
+        }
+        setPrimaryClipInternalLocked(clipboard, clip, uid, sourcePackage);
 
         // Update related users
         List<UserInfo> related = getRelatedProfiles(userId);
@@ -911,8 +942,11 @@
                         final boolean canCopyIntoProfile = !hasRestriction(
                                 UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                         if (canCopyIntoProfile) {
-                            setPrimaryClipInternalNoClassifyLocked(
-                                    getClipboardLocked(id, deviceId), clip, uid, sourcePackage);
+                            Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
+                            if (relatedClipboard != null) {
+                                setPrimaryClipInternalNoClassifyLocked(relatedClipboard, clip, uid,
+                                        sourcePackage);
+                            }
                         }
                     }
                 }
@@ -1046,6 +1080,9 @@
 
         synchronized (mLock) {
             Clipboard clipboard = getClipboardLocked(userId, deviceId);
+            if (clipboard == null) {
+                return;
+            }
             if (clipboard.primaryClip == clip) {
                 applyClassificationAndSendBroadcastLocked(
                         clipboard, confidences, links, classifier);
@@ -1061,7 +1098,8 @@
                                     UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                             if (canCopyIntoProfile) {
                                 Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
-                                if (hasTextLocked(relatedClipboard, text)) {
+                                if (relatedClipboard != null
+                                        && hasTextLocked(relatedClipboard, text)) {
                                     applyClassificationAndSendBroadcastLocked(
                                             relatedClipboard, confidences, links, classifier);
                                 }
@@ -1184,9 +1222,10 @@
             Binder.restoreCallingIdentity(oldIdentity);
         }
         Clipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid), deviceId);
-        if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
+        if (clipboard != null && clipboard.primaryClip != null
+                && !clipboard.activePermissionOwners.contains(pkg)) {
             final int N = clipboard.primaryClip.getItemCount();
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
                         pkg, UserHandle.getUserId(uid));
             }
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 5e62b56..2206eac 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -35,7 +35,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
-import android.sysprop.ApexProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -82,18 +81,12 @@
             new Singleton<ApexManager>() {
                 @Override
                 protected ApexManager create() {
-                    if (ApexProperties.updatable().orElse(false)) {
-                        return new ApexManagerImpl();
-                    } else {
-                        return new ApexManagerFlattenedApex();
-                    }
+                    return new ApexManagerImpl();
                 }
             };
 
     /**
-     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
-     * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
-     * evaluates to {@code true}.
+     * Returns an instance of {@link ApexManagerImpl}
      * @hide
      */
     public static ApexManager getInstance() {
@@ -991,203 +984,4 @@
             }
         }
     }
-
-    /**
-     * An implementation of {@link ApexManager} that should be used in case device does not support
-     * updating APEX packages.
-     */
-    @VisibleForTesting
-    static final class ApexManagerFlattenedApex extends ApexManager {
-        @Override
-        ApexInfo[] getAllApexInfos() {
-            return null;
-        }
-
-        @Override
-        void notifyScanResult(List<ScanResult> scanResults) {
-            // No-op
-        }
-
-        @Override
-        public List<ActiveApexInfo> getActiveApexInfos() {
-            // There is no apexd running in case of flattened apex
-            // We look up the /apex directory and identify the active APEX modules from there.
-            // As "preinstalled" path, we just report /system since in the case of flattened APEX
-            // the /apex directory is just a symlink to /system/apex.
-            List<ActiveApexInfo> result = new ArrayList<>();
-            File apexDir = Environment.getApexDirectory();
-            if (apexDir.isDirectory()) {
-                File[] files = apexDir.listFiles();
-                // listFiles might be null if system server doesn't have permission to read
-                // a directory.
-                if (files != null) {
-                    for (File file : files) {
-                        if (file.isDirectory() && !file.getName().contains("@")
-                                // In flattened configuration, init special-cases the art directory
-                                // and bind-mounts com.android.art.debug to com.android.art.
-                                && !file.getName().equals("com.android.art.debug")) {
-                            result.add(
-                                    new ActiveApexInfo(file, Environment.getRootDirectory(), file));
-                        }
-                    }
-                }
-            }
-            return result;
-        }
-
-        @Override
-        @Nullable
-        public String getActiveApexPackageNameContainingPackage(
-                @NonNull String containedPackageName) {
-            Objects.requireNonNull(containedPackageName);
-
-            return null;
-        }
-
-        @Override
-        ApexSessionInfo getStagedSessionInfo(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        SparseArray<ApexSessionInfo> getSessions() {
-            return new SparseArray<>(0);
-        }
-
-        @Override
-        ApexInfoList submitStagedSession(ApexSessionParams params)
-                throws PackageManagerException {
-            throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
-                    "Device doesn't support updating APEX");
-        }
-
-        @Override
-        ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void markStagedSessionReady(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void markStagedSessionSuccessful(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean isApexSupported() {
-            return false;
-        }
-
-        @Override
-        boolean revertActiveSessions() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean abortStagedSession(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean uninstallApex(String apexPackagePath) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void registerApkInApex(AndroidPackage pkg) {
-            // No-op
-        }
-
-        @Override
-        void reportErrorWithApkInApex(String scanDirPath, String errorMsg) {
-            // No-op
-        }
-
-        @Override
-        @Nullable
-        String getApkInApexInstallError(String apexPackageName) {
-            return null;
-        }
-
-        @Override
-        List<String> getApksInApex(String apexPackageName) {
-            return Collections.emptyList();
-        }
-
-        @Override
-        @Nullable
-        public String getApexModuleNameForPackageName(String apexPackageName) {
-            return null;
-        }
-
-        @Override
-        @Nullable
-        public String getActivePackageNameForApexModuleName(String apexModuleName) {
-            return null;
-        }
-
-        @Override
-        public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean destroyDeSnapshots(int rollbackId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean destroyCeSnapshots(int userId, int rollbackId) {
-            return true;
-        }
-
-        @Override
-        public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
-            return true;
-        }
-
-        @Override
-        public void markBootCompleted() {
-            // No-op
-        }
-
-        @Override
-        public long calculateSizeForCompressedApex(CompressedApexInfoList infoList) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void reserveSpaceForCompressedApex(CompressedApexInfoList infoList) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        ApexInfo installPackage(File apexFile) {
-            throw new UnsupportedOperationException("APEX updates are not supported");
-        }
-
-        @Override
-        public List<ApexSystemServiceInfo> getApexSystemServices() {
-            // TODO(satayev): we can't really support flattened apex use case, and need to migrate
-            // the manifest entries into system's manifest asap.
-            return Collections.emptyList();
-        }
-
-        @Override
-        void dump(PrintWriter pw) {
-        }
-
-        @Override
-        public File getBackingApexFile(File file) {
-            return null;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 86c4985..375ef61 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -52,7 +52,6 @@
 import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
-import android.sysprop.ApexProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.FastImmutableArraySet;
@@ -921,10 +920,6 @@
         return rebootWithLskfImpl(packageName, reason, slotSwitch);
     }
 
-    public static boolean isUpdatableApexSupported() {
-        return ApexProperties.updatable().orElse(false);
-    }
-
     // Metadata should be no more than few MB, if it's larger than 100MB something is wrong.
     private static final long APEX_INFO_SIZE_LIMIT = 24 * 1024 * 100;
 
@@ -975,11 +970,6 @@
     @Override
     public boolean allocateSpaceForUpdate(String packageFile) {
         allocateSpaceForUpdate_enforcePermission();
-        if (!isUpdatableApexSupported()) {
-            Log.i(TAG, "Updatable Apex not supported, "
-                    + "allocateSpaceForUpdate does nothing.");
-            return true;
-        }
         final long token = Binder.clearCallingIdentity();
         try {
             CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5165e86..35ff3ee 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2129,8 +2129,7 @@
         hasBeenLaunched = false;
         mTaskSupervisor = supervisor;
 
-        info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid,
-                info.launchMode, mActivityComponent);
+        info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid);
         taskAffinity = info.taskAffinity;
         final String uid = Integer.toString(info.applicationInfo.uid);
         if (info.windowLayout != null && info.windowLayout.windowLayoutAffinity != null
@@ -2230,19 +2229,12 @@
      *
      * @param affinity The affinity of the activity.
      * @param uid The user-ID that has been assigned to this application.
-     * @param launchMode The activity launch mode
-     * @param componentName The activity component name. This is only useful when the given
-     *                      launchMode is {@link ActivityInfo#LAUNCH_SINGLE_INSTANCE}
      * @return The task affinity
      */
-    static String computeTaskAffinity(String affinity, int uid, int launchMode,
-            ComponentName componentName) {
+    static String computeTaskAffinity(String affinity, int uid) {
         final String uidStr = Integer.toString(uid);
         if (affinity != null && !affinity.startsWith(uidStr)) {
             affinity = uidStr + ":" + affinity;
-            if (launchMode == LAUNCH_SINGLE_INSTANCE && componentName != null) {
-                affinity += ":" + componentName.hashCode();
-            }
         }
         return affinity;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cbfed6c..b3a05f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -522,7 +522,6 @@
     boolean mWaitingForConfig;
 
     // TODO(multi-display): remove some of the usages.
-    @VisibleForTesting
     boolean isDefaultDisplay;
 
     /** Detect user tapping outside of current focused task bounds .*/
@@ -3358,6 +3357,7 @@
             mRootWindowContainer.mTaskSupervisor
                     .getKeyguardController().onDisplayRemoved(mDisplayId);
             mWallpaperController.resetLargestDisplay(mDisplay);
+            mWmService.mDisplayWindowSettings.onDisplayRemoved(this);
         } finally {
             mDisplayReady = false;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 4c0435e..e1753d7 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -33,6 +33,7 @@
 import android.view.DisplayInfo;
 import android.view.IWindowManager;
 import android.view.Surface;
+import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 
 import com.android.server.policy.WindowManagerPolicy;
@@ -45,15 +46,20 @@
  * delegates the persistence and lookup of settings values to the supplied {@link SettingsProvider}.
  */
 class DisplayWindowSettings {
+    @NonNull
     private final WindowManagerService mService;
+    @NonNull
     private final SettingsProvider mSettingsProvider;
 
-    DisplayWindowSettings(WindowManagerService service, SettingsProvider settingsProvider) {
+    DisplayWindowSettings(@NonNull WindowManagerService service,
+            @NonNull SettingsProvider settingsProvider) {
         mService = service;
         mSettingsProvider = settingsProvider;
     }
 
-    void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
+    void setUserRotation(@NonNull DisplayContent displayContent,
+            @WindowManagerPolicy.UserRotationMode int rotationMode,
+            @Surface.Rotation int rotation) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -62,7 +68,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setForcedSize(DisplayContent displayContent, int width, int height) {
+    void setForcedSize(@NonNull DisplayContent displayContent, int width, int height) {
         if (displayContent.isDefaultDisplay) {
             final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
             Settings.Global.putString(mService.mContext.getContentResolver(),
@@ -77,21 +83,20 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setForcedDensity(DisplayInfo info, int density, int userId) {
+    void setForcedDensity(@NonNull DisplayInfo info, int density, int userId) {
         if (info.displayId == Display.DEFAULT_DISPLAY) {
             final String densityString = density == 0 ? "" : Integer.toString(density);
             Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
                     Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
         }
 
-        final DisplayInfo displayInfo = info;
         final SettingsProvider.SettingsEntry overrideSettings =
-                mSettingsProvider.getOverrideSettings(displayInfo);
+                mSettingsProvider.getOverrideSettings(info);
         overrideSettings.mForcedDensity = density;
-        mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+        mSettingsProvider.updateOverrideSettings(info, overrideSettings);
     }
 
-    void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
+    void setForcedScalingMode(@NonNull DisplayContent displayContent, @ForceScalingMode int mode) {
         if (displayContent.isDefaultDisplay) {
             Settings.Global.putInt(mService.mContext.getContentResolver(),
                     Settings.Global.DISPLAY_SCALING_FORCE, mode);
@@ -104,7 +109,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setFixedToUserRotation(DisplayContent displayContent, int fixedToUserRotation) {
+    void setFixedToUserRotation(@NonNull DisplayContent displayContent, int fixedToUserRotation) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -112,8 +117,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setIgnoreOrientationRequest(
-            DisplayContent displayContent, boolean ignoreOrientationRequest) {
+    void setIgnoreOrientationRequest(@NonNull DisplayContent displayContent,
+            boolean ignoreOrientationRequest) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -121,7 +126,9 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    private int getWindowingModeLocked(SettingsProvider.SettingsEntry settings, DisplayContent dc) {
+    @WindowConfiguration.WindowingMode
+    private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
+            @NonNull DisplayContent dc) {
         int windowingMode = settings.mWindowingMode;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
         // back to fullscreen.
@@ -139,13 +146,15 @@
         return windowingMode;
     }
 
-    int getWindowingModeLocked(DisplayContent dc) {
+    @WindowConfiguration.WindowingMode
+    int getWindowingModeLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         return getWindowingModeLocked(settings, dc);
     }
 
-    void setWindowingModeLocked(DisplayContent dc, int mode) {
+    void setWindowingModeLocked(@NonNull DisplayContent dc,
+            @WindowConfiguration.WindowingMode int mode) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -157,7 +166,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    int getRemoveContentModeLocked(DisplayContent dc) {
+    @WindowManager.RemoveContentMode
+    int getRemoveContentModeLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
@@ -171,7 +181,8 @@
         return settings.mRemoveContentMode;
     }
 
-    void setRemoveContentModeLocked(DisplayContent dc, int mode) {
+    void setRemoveContentModeLocked(@NonNull DisplayContent dc,
+            @WindowManager.RemoveContentMode int mode) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -179,14 +190,14 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
+    boolean shouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         return settings.mShouldShowWithInsecureKeyguard != null
                 ? settings.mShouldShowWithInsecureKeyguard : false;
     }
 
-    void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
+    void setShouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc, boolean shouldShow) {
         if (!dc.isPrivate() && shouldShow) {
             throw new IllegalArgumentException("Public display can't be allowed to show content"
                     + " when locked");
@@ -199,7 +210,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setDontMoveToTop(DisplayContent dc, boolean dontMoveToTop) {
+    void setDontMoveToTop(@NonNull DisplayContent dc, boolean dontMoveToTop) {
         DisplayInfo displayInfo = dc.getDisplayInfo();
         SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getSettings(displayInfo);
@@ -207,7 +218,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
+    boolean shouldShowSystemDecorsLocked(@NonNull DisplayContent dc) {
         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
             // Default display should show system decors.
             return true;
@@ -218,7 +229,7 @@
         return settings.mShouldShowSystemDecors != null ? settings.mShouldShowSystemDecors : false;
     }
 
-    void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
+    void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -226,7 +237,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    @DisplayImePolicy int getImePolicyLocked(DisplayContent dc) {
+    @DisplayImePolicy
+    int getImePolicyLocked(@NonNull DisplayContent dc) {
         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
             // Default display should show IME.
             return DISPLAY_IME_POLICY_LOCAL;
@@ -238,7 +250,7 @@
                 : DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
     }
 
-    void setDisplayImePolicy(DisplayContent dc, @DisplayImePolicy int imePolicy) {
+    void setDisplayImePolicy(@NonNull DisplayContent dc, @DisplayImePolicy int imePolicy) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -246,11 +258,11 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void applySettingsToDisplayLocked(DisplayContent dc) {
+    void applySettingsToDisplayLocked(@NonNull DisplayContent dc) {
         applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true);
     }
 
-    void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) {
+    void applySettingsToDisplayLocked(@NonNull DisplayContent dc, boolean includeRotationSettings) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
 
@@ -274,9 +286,8 @@
         dc.mIsDensityForced = hasDensityOverride;
         dc.mIsSizeForced = hasSizeOverride;
 
-        final boolean ignoreDisplayCutout = settings.mIgnoreDisplayCutout != null
+        dc.mIgnoreDisplayCutout = settings.mIgnoreDisplayCutout != null
                 ? settings.mIgnoreDisplayCutout : false;
-        dc.mIgnoreDisplayCutout = ignoreDisplayCutout;
 
         final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth;
         final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight;
@@ -296,7 +307,7 @@
         if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
     }
 
-    void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
+    void applyRotationSettingsToDisplayLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
 
@@ -315,7 +326,7 @@
      * @return {@code true} if any settings for this display has changed; {@code false} if nothing
      * changed.
      */
-    boolean updateSettingsForDisplay(DisplayContent dc) {
+    boolean updateSettingsForDisplay(@NonNull DisplayContent dc) {
         final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
         if (defaultTda != null && defaultTda.getWindowingMode() != getWindowingModeLocked(dc)) {
             // For the time being the only thing that may change is windowing mode, so just update
@@ -327,6 +338,13 @@
     }
 
     /**
+     * Called when the given {@link DisplayContent} is removed to cleanup.
+     */
+    void onDisplayRemoved(@NonNull DisplayContent dc) {
+        mSettingsProvider.onDisplayRemoved(dc.getDisplayInfo());
+    }
+
+    /**
      * Provides the functionality to lookup the {@link SettingsEntry settings} for a given
      * {@link DisplayInfo}.
      * <p>
@@ -364,19 +382,29 @@
         void updateOverrideSettings(@NonNull DisplayInfo info, @NonNull SettingsEntry overrides);
 
         /**
+         * Called when a display is removed to cleanup.
+         */
+        void onDisplayRemoved(@NonNull DisplayInfo info);
+
+        /**
          * Settings for a display.
          */
         class SettingsEntry {
+            @WindowConfiguration.WindowingMode
             int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
             @Nullable
+            @WindowManagerPolicy.UserRotationMode
             Integer mUserRotationMode;
             @Nullable
+            @Surface.Rotation
             Integer mUserRotation;
             int mForcedWidth;
             int mForcedHeight;
             int mForcedDensity;
             @Nullable
+            @ForceScalingMode
             Integer mForcedScalingMode;
+            @WindowManager.RemoveContentMode
             int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED;
             @Nullable
             Boolean mShouldShowWithInsecureKeyguard;
@@ -395,7 +423,7 @@
 
             SettingsEntry() {}
 
-            SettingsEntry(SettingsEntry copyFrom) {
+            SettingsEntry(@NonNull SettingsEntry copyFrom) {
                 setTo(copyFrom);
             }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 1abb0a1..ea668fa 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.TYPE_VIRTUAL;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
@@ -28,6 +29,8 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -49,7 +52,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -86,7 +88,9 @@
         void finishWrite(OutputStream os, boolean success);
     }
 
+    @NonNull
     private ReadableSettings mBaseSettings;
+    @NonNull
     private final WritableSettings mOverrideSettings;
 
     DisplayWindowSettingsProvider() {
@@ -155,6 +159,16 @@
         mOverrideSettings.updateSettingsEntry(info, overrides);
     }
 
+    @Override
+    public void onDisplayRemoved(@NonNull DisplayInfo info) {
+        mOverrideSettings.onDisplayRemoved(info);
+    }
+
+    @VisibleForTesting
+    int getOverrideSettingsSize() {
+        return mOverrideSettings.mSettings.size();
+    }
+
     /**
      * Class that allows reading {@link SettingsEntry entries} from a
      * {@link ReadableSettingsStorage}.
@@ -168,14 +182,15 @@
          */
         @DisplayIdentifierType
         protected int mIdentifierType;
-        protected final Map<String, SettingsEntry> mSettings = new HashMap<>();
+        @NonNull
+        protected final ArrayMap<String, SettingsEntry> mSettings = new ArrayMap<>();
 
-        ReadableSettings(ReadableSettingsStorage settingsStorage) {
+        ReadableSettings(@NonNull ReadableSettingsStorage settingsStorage) {
             loadSettings(settingsStorage);
         }
 
         @Nullable
-        final SettingsEntry getSettingsEntry(DisplayInfo info) {
+        final SettingsEntry getSettingsEntry(@NonNull DisplayInfo info) {
             final String identifier = getIdentifier(info);
             SettingsEntry settings;
             // Try to get corresponding settings using preferred identifier for the current config.
@@ -193,7 +208,8 @@
         }
 
         /** Gets the identifier of choice for the current config. */
-        protected final String getIdentifier(DisplayInfo displayInfo) {
+        @NonNull
+        protected final String getIdentifier(@NonNull DisplayInfo displayInfo) {
             if (mIdentifierType == IDENTIFIER_PORT && displayInfo.address != null) {
                 // Config suggests using port as identifier for physical displays.
                 if (displayInfo.address instanceof DisplayAddress.Physical) {
@@ -203,7 +219,7 @@
             return displayInfo.uniqueId;
         }
 
-        private void loadSettings(ReadableSettingsStorage settingsStorage) {
+        private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) {
             FileData fileData = readSettings(settingsStorage);
             if (fileData != null) {
                 mIdentifierType = fileData.mIdentifierType;
@@ -217,15 +233,18 @@
      * {@link WritableSettingsStorage}.
      */
     private static final class WritableSettings extends ReadableSettings {
+        @NonNull
         private final WritableSettingsStorage mSettingsStorage;
+        @NonNull
+        private final ArraySet<String> mVirtualDisplayIdentifiers = new ArraySet<>();
 
-        WritableSettings(WritableSettingsStorage settingsStorage) {
+        WritableSettings(@NonNull WritableSettingsStorage settingsStorage) {
             super(settingsStorage);
             mSettingsStorage = settingsStorage;
         }
 
         @NonNull
-        SettingsEntry getOrCreateSettingsEntry(DisplayInfo info) {
+        SettingsEntry getOrCreateSettingsEntry(@NonNull DisplayInfo info) {
             final String identifier = getIdentifier(info);
             SettingsEntry settings;
             // Try to get corresponding settings using preferred identifier for the current config.
@@ -243,21 +262,47 @@
 
             settings = new SettingsEntry();
             mSettings.put(identifier, settings);
+            if (info.type == TYPE_VIRTUAL) {
+                // Keep track of virtual display. We don't want to write virtual display settings to
+                // file.
+                mVirtualDisplayIdentifiers.add(identifier);
+            }
             return settings;
         }
 
-        void updateSettingsEntry(DisplayInfo info, SettingsEntry settings) {
+        void updateSettingsEntry(@NonNull DisplayInfo info, @NonNull SettingsEntry settings) {
             final SettingsEntry overrideSettings = getOrCreateSettingsEntry(info);
             final boolean changed = overrideSettings.setTo(settings);
-            if (changed) {
+            if (changed && info.type != TYPE_VIRTUAL) {
                 writeSettings();
             }
         }
 
+        void onDisplayRemoved(@NonNull DisplayInfo info) {
+            final String identifier = getIdentifier(info);
+            if (!mSettings.containsKey(identifier)) {
+                return;
+            }
+            if (mVirtualDisplayIdentifiers.remove(identifier)
+                    || mSettings.get(identifier).isEmpty()) {
+                // Don't keep track of virtual display or empty settings to avoid growing the cached
+                // map.
+                mSettings.remove(identifier);
+            }
+        }
+
         private void writeSettings() {
-            FileData fileData = new FileData();
+            final FileData fileData = new FileData();
             fileData.mIdentifierType = mIdentifierType;
-            fileData.mSettings.putAll(mSettings);
+            final int size = mSettings.size();
+            for (int i = 0; i < size; i++) {
+                final String identifier = mSettings.keyAt(i);
+                if (mVirtualDisplayIdentifiers.contains(identifier)) {
+                    // Do not write virtual display settings to file.
+                    continue;
+                }
+                fileData.mSettings.put(identifier, mSettings.get(identifier));
+            }
             DisplayWindowSettingsProvider.writeSettings(mSettingsStorage, fileData);
         }
     }
@@ -283,7 +328,7 @@
     }
 
     @Nullable
-    private static FileData readSettings(ReadableSettingsStorage storage) {
+    private static FileData readSettings(@NonNull ReadableSettingsStorage storage) {
         InputStream stream;
         try {
             stream = storage.openRead();
@@ -348,13 +393,14 @@
         return fileData;
     }
 
-    private static int getIntAttribute(TypedXmlPullParser parser, String name, int defaultValue) {
+    private static int getIntAttribute(@NonNull TypedXmlPullParser parser, @NonNull String name,
+            int defaultValue) {
         return parser.getAttributeInt(null, name, defaultValue);
     }
 
     @Nullable
-    private static Integer getIntegerAttribute(TypedXmlPullParser parser, String name,
-            @Nullable Integer defaultValue) {
+    private static Integer getIntegerAttribute(@NonNull TypedXmlPullParser parser,
+            @NonNull String name, @Nullable Integer defaultValue) {
         try {
             return parser.getAttributeInt(null, name);
         } catch (Exception ignored) {
@@ -363,8 +409,8 @@
     }
 
     @Nullable
-    private static Boolean getBooleanAttribute(TypedXmlPullParser parser, String name,
-            @Nullable Boolean defaultValue) {
+    private static Boolean getBooleanAttribute(@NonNull TypedXmlPullParser parser,
+            @NonNull String name, @Nullable Boolean defaultValue) {
         try {
             return parser.getAttributeBoolean(null, name);
         } catch (Exception ignored) {
@@ -372,7 +418,7 @@
         }
     }
 
-    private static void readDisplay(TypedXmlPullParser parser, FileData fileData)
+    private static void readDisplay(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
             throws NumberFormatException, XmlPullParserException, IOException {
         String name = parser.getAttributeValue(null, "name");
         if (name != null) {
@@ -420,7 +466,7 @@
         XmlUtils.skipCurrentTag(parser);
     }
 
-    private static void readConfig(TypedXmlPullParser parser, FileData fileData)
+    private static void readConfig(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
             throws NumberFormatException,
             XmlPullParserException, IOException {
         fileData.mIdentifierType = getIntAttribute(parser, "identifier",
@@ -428,7 +474,8 @@
         XmlUtils.skipCurrentTag(parser);
     }
 
-    private static void writeSettings(WritableSettingsStorage storage, FileData data) {
+    private static void writeSettings(@NonNull WritableSettingsStorage storage,
+            @NonNull FileData data) {
         OutputStream stream;
         try {
             stream = storage.startWrite();
@@ -525,7 +572,8 @@
 
     private static final class FileData {
         int mIdentifierType;
-        final Map<String, SettingsEntry> mSettings = new HashMap<>();
+        @NonNull
+        final Map<String, SettingsEntry> mSettings = new ArrayMap<>();
 
         @Override
         public String toString() {
@@ -537,6 +585,7 @@
     }
 
     private static final class AtomicFileStorage implements WritableSettingsStorage {
+        @NonNull
         private final AtomicFile mAtomicFile;
 
         AtomicFileStorage(@NonNull AtomicFile atomicFile) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9ef5ed0..b71d918b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -183,6 +183,8 @@
 
     /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
     private final ArrayList<Task> mHiddenTasks = new ArrayList<>();
+    /** The maximum size that the hidden tasks are cached. */
+    private static final int MAX_HIDDEN_TASK_SIZE = 10;
 
     /** Whether to trim inactive tasks when activities are idle. */
     private boolean mCheckTrimmableTasksOnIdle;
@@ -1477,9 +1479,13 @@
         return task.compareTo(rootHomeTask) < 0;
     }
 
-    /** Remove the tasks that user may not be able to return. */
+    /** Remove the tasks that user may not be able to return when exceeds the cache limit. */
     private void removeUnreachableHiddenTasks(int windowingMode) {
-        for (int i = mHiddenTasks.size() - 1; i >= 0; i--) {
+        final int size = mHiddenTasks.size();
+        if (size <= MAX_HIDDEN_TASK_SIZE) {
+            return;
+        }
+        for (int i = size - 1; i >= MAX_HIDDEN_TASK_SIZE; i--) {
             final Task hiddenTask = mHiddenTasks.get(i);
             if (!hiddenTask.hasChild() || hiddenTask.inRecents) {
                 // The task was removed by other path or it became reachable (added to recents).
@@ -1523,7 +1529,7 @@
                 // A non-empty task is replaced by a new task. Because the removed task is no longer
                 // managed by the recent tasks list, add it to the hidden list to prevent the task
                 // from becoming dangling.
-                mHiddenTasks.add(removedTask);
+                mHiddenTasks.add(0, removedTask);
             }
             notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
             if (DEBUG_RECENTS_TRIM_TASKS) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c23beb..92e90ae 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5406,8 +5406,7 @@
         // Basic case: for simple app-centric recents, we need to recreate
         // the task if the affinity has changed.
 
-        final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid(),
-                srec.launchMode, srec.mActivityComponent);
+        final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid());
         if (srec == null || srec.getTask().affinity == null
                 || !srec.getTask().affinity.equals(affinity)) {
             return true;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0248acc..358ee87 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -723,6 +723,14 @@
             mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
             return;
         }
+        // Activity doesn't need to capture snapshot if the starting window has associated to task.
+        if (wc.asActivityRecord() != null) {
+            final ActivityRecord activityRecord = wc.asActivityRecord();
+            if (activityRecord.mStartingData != null
+                    && activityRecord.mStartingData.mAssociatedTask != null) {
+                return;
+            }
+        }
 
         if (mContainerFreezer == null) {
             mContainerFreezer = new ScreenshotFreezer();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fa279a8..db92191 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -552,7 +552,9 @@
     final PackageManagerInternal mPmInternal;
     private final TestUtilityService mTestUtilityService;
 
+    @NonNull
     final DisplayWindowSettingsProvider mDisplayWindowSettingsProvider;
+    @NonNull
     final DisplayWindowSettings mDisplayWindowSettings;
 
     /** If the system should display notifications for apps displaying an alert window. */
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index f660b42..5e7dd59 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -85,7 +85,7 @@
 //TODO Introduce a single lock object to lock the whole state and avoid the requirement above.
 
 // All users should be able to connect to USB and Bluetooth MIDI devices.
-// All users can create can install an app that provides, a Virtual MIDI Device Service.
+// All users can create can install an app that provides a Virtual MIDI Device Service.
 // Users can not open virtual MIDI devices created by other users.
 // getDevices() surfaces devices that can be opened by that user.
 // openDevice() rejects devices that are cannot be opened by that user.
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 292320e..885ed35 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -63,9 +63,8 @@
 
     private static final String TAG = "PeopleService";
 
-    private DataManager mDataManager;
-    @VisibleForTesting
-    ConversationListenerHelper mConversationListenerHelper;
+    private DataManager mLazyDataManager;
+    private ConversationListenerHelper mLazyConversationListenerHelper;
 
     private PackageManagerInternal mPackageManagerInternal;
 
@@ -76,17 +75,30 @@
      */
     public PeopleService(Context context) {
         super(context);
-
-        mDataManager = new DataManager(context);
-        mConversationListenerHelper = new ConversationListenerHelper();
-        mDataManager.addConversationsListener(mConversationListenerHelper);
     }
 
-    @Override
-    public void onBootPhase(int phase) {
-        if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            mDataManager.initialize();
+    @VisibleForTesting
+    ConversationListenerHelper getConversationListenerHelper() {
+        if (mLazyConversationListenerHelper == null) {
+            initLazyStuff();
         }
+        return mLazyConversationListenerHelper;
+    }
+
+    private synchronized void initLazyStuff() {
+        if (mLazyDataManager == null) {
+            mLazyDataManager = new DataManager(getContext());
+            mLazyDataManager.initialize();
+            mLazyConversationListenerHelper = new ConversationListenerHelper();
+            mLazyDataManager.addConversationsListener(mLazyConversationListenerHelper);
+        }
+    }
+
+    private DataManager getDataManager() {
+        if (mLazyDataManager == null) {
+            initLazyStuff();
+        }
+        return mLazyDataManager;
     }
 
     @Override
@@ -105,12 +117,12 @@
 
     @Override
     public void onUserUnlocked(@NonNull TargetUser user) {
-        mDataManager.onUserUnlocked(user.getUserIdentifier());
+        getDataManager().onUserUnlocked(user.getUserIdentifier());
     }
 
     @Override
     public void onUserStopping(@NonNull TargetUser user) {
-        mDataManager.onUserStopping(user.getUserIdentifier());
+        getDataManager().onUserStopping(user.getUserIdentifier());
     }
 
     /**
@@ -171,28 +183,28 @@
         public ConversationChannel getConversation(
                 String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get conversation");
-            return mDataManager.getConversation(packageName, userId, shortcutId);
+            return getDataManager().getConversation(packageName, userId, shortcutId);
         }
 
         @Override
         public ParceledListSlice<ConversationChannel> getRecentConversations() {
             enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
             return new ParceledListSlice<>(
-                    mDataManager.getRecentConversations(
+                    getDataManager().getRecentConversations(
                             Binder.getCallingUserHandle().getIdentifier()));
         }
 
         @Override
         public void removeRecentConversation(String packageName, int userId, String shortcutId) {
             enforceSystemOrRoot("remove a recent conversation");
-            mDataManager.removeRecentConversation(packageName, userId, shortcutId,
+            getDataManager().removeRecentConversation(packageName, userId, shortcutId,
                     Binder.getCallingUserHandle().getIdentifier());
         }
 
         @Override
         public void removeAllRecentConversations() {
             enforceSystemOrRoot("remove all recent conversations");
-            mDataManager.removeAllRecentConversations(
+            getDataManager().removeAllRecentConversations(
                     Binder.getCallingUserHandle().getIdentifier());
         }
 
@@ -200,7 +212,7 @@
         public boolean isConversation(String packageName, int userId, String shortcutId) {
             enforceHasReadPeopleDataPermission();
             handleIncomingUser(userId);
-            return mDataManager.isConversation(packageName, userId, shortcutId);
+            return getDataManager().isConversation(packageName, userId, shortcutId);
         }
 
         private void enforceHasReadPeopleDataPermission() throws SecurityException {
@@ -213,7 +225,7 @@
         @Override
         public long getLastInteraction(String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get last interaction");
-            return mDataManager.getLastInteraction(packageName, userId, shortcutId);
+            return getDataManager().getLastInteraction(packageName, userId, shortcutId);
         }
 
         @Override
@@ -224,7 +236,7 @@
             if (status.getStartTimeMillis() > System.currentTimeMillis()) {
                 throw new IllegalArgumentException("Start time must be in the past");
             }
-            mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status);
+            getDataManager().addOrUpdateStatus(packageName, userId, conversationId, status);
         }
 
         @Override
@@ -232,14 +244,14 @@
                 String statusId) {
             handleIncomingUser(userId);
             checkCallerIsSameApp(packageName);
-            mDataManager.clearStatus(packageName, userId, conversationId, statusId);
+            getDataManager().clearStatus(packageName, userId, conversationId, statusId);
         }
 
         @Override
         public void clearStatuses(String packageName, int userId, String conversationId) {
             handleIncomingUser(userId);
             checkCallerIsSameApp(packageName);
-            mDataManager.clearStatuses(packageName, userId, conversationId);
+            getDataManager().clearStatuses(packageName, userId, conversationId);
         }
 
         @Override
@@ -250,21 +262,21 @@
                 checkCallerIsSameApp(packageName);
             }
             return new ParceledListSlice<>(
-                    mDataManager.getStatuses(packageName, userId, conversationId));
+                    getDataManager().getStatuses(packageName, userId, conversationId));
         }
 
         @Override
         public void registerConversationListener(
                 String packageName, int userId, String shortcutId, IConversationListener listener) {
             enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
-            mConversationListenerHelper.addConversationListener(
+            getConversationListenerHelper().addConversationListener(
                     new ListenerKey(packageName, userId, shortcutId), listener);
         }
 
         @Override
         public void unregisterConversationListener(IConversationListener listener) {
             enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
-            mConversationListenerHelper.removeConversationListener(listener);
+            getConversationListenerHelper().removeConversationListener(listener);
         }
     };
 
@@ -393,7 +405,7 @@
         public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
                 AppPredictionSessionId sessionId) {
             mSessions.put(sessionId,
-                    new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+                    new SessionInfo(appPredictionContext, getDataManager(), sessionId.getUserId(),
                             getContext()));
         }
 
@@ -448,18 +460,18 @@
 
         @Override
         public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
-            mDataManager.pruneDataForUser(userId, signal);
+            getDataManager().pruneDataForUser(userId, signal);
         }
 
         @Nullable
         @Override
         public byte[] getBackupPayload(@UserIdInt int userId) {
-            return mDataManager.getBackupPayload(userId);
+            return getDataManager().getBackupPayload(userId);
         }
 
         @Override
         public void restore(@UserIdInt int userId, @NonNull byte[] payload) {
-            mDataManager.restore(userId, payload);
+            getDataManager().restore(userId, payload);
         }
 
         @VisibleForTesting
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 5b0e2f3..1ae9124 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -474,15 +474,6 @@
     }
 
     @Test
-    public void testGetBackingApexFiles_flattenedApex() {
-        ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
-        final File backingApexFile = flattenedApexManager.getBackingApexFile(
-                new File(mMockSystem.system().getApexDirectory(),
-                        "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
-        assertThat(backingApexFile).isNull();
-    }
-
-    @Test
     public void testActiveApexChanged() throws RemoteException {
         ApexInfo apex1 = createApexInfo(
                 "com.apex1", 37, true, true, new File("/data/apex/active/com.apex@37.apex"));
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index 5066240..aed8491 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -41,8 +41,10 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -56,6 +58,7 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,6 +90,13 @@
     private AppPredictionContext mPredictionContext;
 
     @Mock
+    ShortcutServiceInternal mShortcutServiceInternal;
+    @Mock
+    PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    NotificationManagerInternal mNotificationManagerInternal;
+
+    @Mock
     private Context mMockContext;
 
     @Rule
@@ -110,6 +120,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+        LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+
         mPeopleService = new TestablePeopleService(mContext);
         mTestableLooper = TestableLooper.get(this);
         mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
@@ -137,6 +151,9 @@
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(NotificationManagerInternal.class);
     }
 
     @Test
@@ -167,25 +184,29 @@
     @Test
     public void testRegisterConversationListener() throws Exception {
         assertEquals(0,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(1,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(2,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
     }
 
     @Test
@@ -201,20 +222,24 @@
                 listener3);
         mTestableLooper.processAllMessages();
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.unregisterConversationListener(
                 listener2);
         assertEquals(2,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
         mIPeopleManager.unregisterConversationListener(
                 listener1);
         assertEquals(1,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
         mIPeopleManager.unregisterConversationListener(
                 listener3);
         assertEquals(0,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
     }
 
     @Test
@@ -229,12 +254,13 @@
                 PeopleManager.ConversationListener.class);
         registerListener(CONVERSATION_ID_2, listenerForConversation2);
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         // Update conversation with two listeners.
         ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
                 ACTIVITY_GAME).build();
-        mPeopleService.mConversationListenerHelper.onConversationsUpdate(
+        mPeopleService.getConversationListenerHelper().onConversationsUpdate(
                 Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 870dd48..7284744 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1749,8 +1749,7 @@
     public void testLaunchActivityWithoutDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
@@ -1775,8 +1774,7 @@
     public void testLaunchActivityWithDifferentDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
@@ -1801,8 +1799,7 @@
     public void testLaunchActivityWithSameDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 9d839fc..7d9fdd5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.TYPE_VIRTUAL;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -26,6 +27,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
 
 import android.annotation.Nullable;
 import android.platform.test.annotations.Presubmit;
@@ -233,6 +235,22 @@
     }
 
     @Test
+    public void testDoNotWriteVirtualDisplaySettingsToStorage() throws Exception {
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        secondaryDisplayInfo.type = TYPE_VIRTUAL;
+
+        // No write to storage on virtual display change.
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final SettingsEntry virtualSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+        virtualSettings.mShouldShowSystemDecors = true;
+        virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
+        virtualSettings.mDontMoveToTop = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, virtualSettings);
+        assertFalse(mOverrideSettingsStorage.wasWriteSuccessful());
+    }
+
+    @Test
     public void testWritingDisplaySettingsToStorage_UsePortAsId() throws Exception {
         prepareOverrideDisplaySettings(null /* displayIdentifier */, true /* usePortAsId */);
 
@@ -260,6 +278,54 @@
                 getStoredDisplayAttributeValue(mOverrideSettingsStorage, "imePolicy"));
     }
 
+    @Test
+    public void testCleanUpEmptyDisplaySettingsOnDisplayRemoved() {
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final int initialSize = provider.getOverrideSettingsSize();
+
+        // Size + 1 when query for a new display.
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a display is removed, its override Settings is not removed if there is any override.
+        overrideSettings.mShouldShowSystemDecors = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a display is removed, its override Settings is removed if there is no override.
+        provider.updateOverrideSettings(secondaryDisplayInfo, new SettingsEntry());
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize, provider.getOverrideSettingsSize());
+    }
+
+    @Test
+    public void testCleanUpVirtualDisplaySettingsOnDisplayRemoved() {
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final int initialSize = provider.getOverrideSettingsSize();
+
+        // Size + 1 when query for a new display.
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        secondaryDisplayInfo.type = TYPE_VIRTUAL;
+        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a virtual display is removed, its override Settings is removed even if it has
+        // override.
+        overrideSettings.mShouldShowSystemDecors = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize, provider.getOverrideSettingsSize());
+    }
+
     /**
      * Prepares display settings and stores in {@link #mOverrideSettingsStorage}. Uses provided
      * display identifier and stores windowingMode=WINDOWING_MODE_PINNED.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 1cec0ef..e54b8e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -484,6 +484,18 @@
         assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest());
     }
 
+    @Test
+    public void testDisplayRemoval() {
+        spyOn(mWm.mDisplayWindowSettings);
+        spyOn(mWm.mDisplayWindowSettingsProvider);
+
+        mPrivateDisplay.removeImmediately();
+
+        verify(mWm.mDisplayWindowSettings).onDisplayRemoved(mPrivateDisplay);
+        verify(mWm.mDisplayWindowSettingsProvider).onDisplayRemoved(
+                mPrivateDisplay.getDisplayInfo());
+    }
+
     public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider {
         Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>();
 
@@ -513,5 +525,10 @@
 
             overrideSettings.setTo(settings);
         }
+
+        @Override
+        public void onDisplayRemoved(@NonNull DisplayInfo info) {
+            mOverrideSettingsCache.remove(info);
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index b02b774..f23e56d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -27,8 +27,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
-import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.os.Process.NOBODY_UID;
 
@@ -451,25 +449,15 @@
         final int uid = 10123;
         final Task task1 = createTaskBuilder(".Task1").build();
         final ComponentName componentName = getUniqueComponentName();
-        task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
-                componentName);
+        task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
         mRecentTasks.add(task1);
 
         // Add another task to recents, and make sure the previous task was removed.
         final Task task2 = createTaskBuilder(".Task2").build();
-        task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
-                componentName);
+        task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
         mRecentTasks.add(task2);
         assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
                 true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
-        // Add another single-instance task to recents, and make sure no task is removed.
-        final Task task3 = createTaskBuilder(".Task3").build();
-        task3.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid,
-                LAUNCH_SINGLE_INSTANCE, componentName);
-        mRecentTasks.add(task3);
-        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
index b2e44b1..e11df98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
@@ -52,6 +52,12 @@
         overrideSettings.setTo(overrides);
     }
 
+    @Override
+    public void onDisplayRemoved(@NonNull DisplayInfo info) {
+        final String identifier = getIdentifier(info);
+        mOverrideSettingsMap.remove(identifier);
+    }
+
     @NonNull
     private SettingsEntry getOrCreateOverrideSettingsEntry(DisplayInfo info) {
         final String identifier = getIdentifier(info);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a2fbc95..882c277 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -772,6 +772,18 @@
         }
     }
 
+    /**
+     * Convert isNonTerrestrialNetwork to string
+     *
+     * @param isNonTerrestrialNetwork boolean indicating whether network is a non-terrestrial
+     *                                network
+     * @return string format of isNonTerrestrialNetwork.
+     * @hide
+     */
+    public static String isNonTerrestrialNetworkToString(boolean isNonTerrestrialNetwork) {
+        return isNonTerrestrialNetwork ? "NON-TERRESTRIAL" : "TERRESTRIAL";
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -797,7 +809,8 @@
                         ? nrStateToString(mNrState) : "****")
                 .append(" rRplmn=").append(mRplmn)
                 .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
-                .append(" isNonTerrestrialNetwork=").append(mIsNonTerrestrialNetwork)
+                .append(" isNonTerrestrialNetwork=").append(
+                        isNonTerrestrialNetworkToString(mIsNonTerrestrialNetwork))
                 .append("}").toString();
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index b18a1a8..4d36111 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -19,6 +19,8 @@
 package com.android.server.wm.flicker
 
 import android.tools.common.PlatformConsts
+import android.tools.common.Position
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.flicker.subject.region.RegionSubject
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentNameMatcher
@@ -169,12 +171,7 @@
  */
 fun LegacyFlickerTest.navBarLayerPositionAtStart() {
     assertLayersStart {
-        val display =
-            this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(
-                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
-            )
+        assertNavBarPosition(this, scenario.isGesturalNavigation)
     }
 }
 
@@ -184,13 +181,36 @@
  */
 fun LegacyFlickerTest.navBarLayerPositionAtEnd() {
     assertLayersEnd {
-        val display =
-            this.entry.displays.minByOrNull { it.id }
-                ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(
-                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
-            )
+        assertNavBarPosition(this, scenario.isGesturalNavigation)
+    }
+}
+
+private fun assertNavBarPosition(sfState: LayerTraceEntrySubject, isGesturalNavigation: Boolean) {
+    val display =
+        sfState.entry.displays.filterNot { it.isOff }.minByOrNull { it.id }
+            ?: error("There is no display!")
+    val displayArea = display.layerStackSpace
+    val navBarPosition = display.navBarPosition(isGesturalNavigation)
+    val navBarRegion = sfState.visibleRegion(ComponentNameMatcher.NAV_BAR)
+
+    when (navBarPosition) {
+        Position.TOP ->
+            navBarRegion.hasSameTopPosition(displayArea)
+                .hasSameLeftPosition(displayArea)
+                .hasSameRightPosition(displayArea)
+        Position.BOTTOM ->
+            navBarRegion.hasSameBottomPosition(displayArea)
+                .hasSameLeftPosition(displayArea)
+                .hasSameRightPosition(displayArea)
+        Position.LEFT ->
+            navBarRegion.hasSameLeftPosition(displayArea)
+                .hasSameTopPosition(displayArea)
+                .hasSameBottomPosition(displayArea)
+        Position.RIGHT ->
+            navBarRegion.hasSameRightPosition(displayArea)
+                .hasSameTopPosition(displayArea)
+                .hasSameBottomPosition(displayArea)
+        else -> error("Unknown position $navBarPosition")
     }
 }
 
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 0d6dc35..7323b0f 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -47,6 +47,7 @@
         "-Wno-missing-field-initializers",
         "-fno-exceptions",
         "-fno-rtti",
+        "-Wno-deprecated-declarations",
     ],
     target: {
         windows: {
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 4cd7eae..27c354a 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -40,10 +40,8 @@
 };
 
 // Info about an APK loaded in memory.
-class LoadedApk {
+class LoadedApk final {
  public:
-  virtual ~LoadedApk() = default;
-
   // Loads both binary and proto APKs from disk.
   static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
                                                     android::IDiagnostics* diag);
@@ -96,8 +94,8 @@
    * Writes the APK on disk at the given path, while also removing the resource
    * files that are not referenced in the resource table.
    */
-  virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
-                              IArchiveWriter* writer);
+  bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
+                      IArchiveWriter* writer);
 
   /**
    * Writes the APK on disk at the given path, while also removing the resource files that are not
@@ -108,9 +106,9 @@
    * original manifest will be written. The manifest is only required if the contents of the new APK
    * have been modified in a way that require the AndroidManifest.xml to also be modified.
    */
-  virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
-                              const TableFlattenerOptions& options, FilterChain* filters,
-                              IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+  bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+                      const TableFlattenerOptions& options, FilterChain* filters,
+                      IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
 
   /** Loads the file as an xml document. */
   std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path,
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index a766bd4..83f2eb3 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -16,20 +16,24 @@
 
 #include "SdkConstants.h"
 
+#include <stdint.h>
+
 #include <algorithm>
 #include <string>
-#include <unordered_set>
-#include <vector>
+#include <string_view>
 
 using android::StringPiece;
+using namespace std::literals;
 
 namespace aapt {
 
-static ApiVersion sDevelopmentSdkLevel = 10000;
-static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>(
-    {"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake", "VanillaIceCream"});
+static constexpr ApiVersion sDevelopmentSdkLevel = 10000;
+static constexpr StringPiece sDevelopmentSdkCodeNames[] = {
+    "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv};
 
-static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
+static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv;
+
+static constexpr std::pair<uint16_t, ApiVersion> sAttrIdMap[] = {
     {0x021c, 1},
     {0x021d, 2},
     {0x0269, SDK_CUPCAKE},
@@ -62,25 +66,37 @@
     {0x064c, SDK_S_V2},
 };
 
-static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
-  return p.first < entryId;
-}
+static_assert(std::is_sorted(std::begin(sAttrIdMap), std::end(sAttrIdMap),
+                             [](auto&& l, auto&& r) { return l.first < r.first; }));
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
   if (id.package_id() != 0x01 || id.type_id() != 0x01) {
     return 0;
   }
-  auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id);
-  if (iter == sAttrIdMap.end()) {
+  const auto it =
+      std::lower_bound(std::begin(sAttrIdMap), std::end(sAttrIdMap), id.entry_id(),
+                       [](const auto& pair, uint16_t entryId) { return pair.first < entryId; });
+  if (it == std::end(sAttrIdMap)) {
     return SDK_LOLLIPOP_MR1;
   }
-  return iter->second;
+  return it->second;
 }
 
 std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
-  return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
-             ? std::optional<ApiVersion>()
-             : sDevelopmentSdkLevel;
+  const auto it =
+      std::find_if(std::begin(sDevelopmentSdkCodeNames), std::end(sDevelopmentSdkCodeNames),
+                   [code_name](const auto& item) { return code_name.starts_with(item); });
+  if (it == std::end(sDevelopmentSdkCodeNames)) {
+    return {};
+  }
+  if (code_name.size() == it->size()) {
+    return sDevelopmentSdkLevel;
+  }
+  if (code_name.size() == it->size() + sPrivacySandboxSuffix.size() &&
+      code_name.ends_with(sPrivacySandboxSuffix)) {
+    return sDevelopmentSdkLevel;
+  }
+  return {};
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index 61f4d71..0f645ff 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -28,4 +28,24 @@
   EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345)));
 }
 
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionValid) {
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("Q"));
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("VanillaIceCream"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionPrivacySandbox) {
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("QPrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(10000),
+            GetDevelopmentSdkCodeNameVersion("VanillaIceCreamPrivacySandbox"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionInvalid) {
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("A"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("Sv3"));
+  EXPECT_EQ(std::optional<ApiVersion>(),
+            GetDevelopmentSdkCodeNameVersion("VanillaIceCream_PrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("PrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("QQQQQQQQQQQQQQQ"));
+}
+
 }  // namespace aapt